/*******************************************************************************
* Copyright (c) 2015 - 2017
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*******************************************************************************/
package jsettlers.logic.map.grid.partition;
import java8.util.Lists;
import jsettlers.algorithms.interfaces.IContainingProvider;
import jsettlers.algorithms.partitions.IBlockingProvider;
import jsettlers.algorithms.partitions.PartitionCalculatorAlgorithm;
import jsettlers.algorithms.traversing.area.AreaTraversingAlgorithm;
import jsettlers.algorithms.traversing.area.IAreaVisitor;
import jsettlers.algorithms.traversing.borders.BorderTraversingAlgorithm;
import jsettlers.common.map.partition.IPartitionData;
import jsettlers.common.map.shapes.FreeMapArea;
import jsettlers.common.map.shapes.IMapArea;
import jsettlers.common.map.shapes.MapCircle;
import jsettlers.common.map.shapes.MapShapeFilter;
import jsettlers.common.movable.EDirection;
import jsettlers.common.position.ILocatable;
import jsettlers.common.position.SRectangle;
import jsettlers.common.position.ShortPoint2D;
import jsettlers.common.utils.Tuple;
import jsettlers.common.utils.coordinates.CoordinateStream;
import jsettlers.common.utils.mutables.MutableInt;
import jsettlers.logic.buildings.MaterialProductionSettings;
import jsettlers.logic.map.grid.partition.PartitionsListingBorderVisitor.BorderPartitionInfo;
import jsettlers.logic.map.grid.partition.manager.PartitionManager;
import jsettlers.logic.map.grid.partition.manager.settings.PartitionManagerSettings;
import jsettlers.logic.player.Player;
import jsettlers.logic.player.PlayerSetting;
import jsettlers.logic.player.Team;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.BitSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import static java8.util.stream.StreamSupport.stream;
/**
* This class handles the partitions of the map.
*
* @author Andreas Eberle
*
*/
public final class PartitionsGrid implements Serializable {
private static final long serialVersionUID = 8919380724171427679L;
private static final int NUMBER_OF_START_PARTITION_OBJECTS = 3000;
private static final float PARTITIONS_EXPAND_FACTOR = 1.5f;
private static final short NO_PLAYER_PARTITION_ID = 0;
private final PartitionOccupyingTowerList occupyingTowers = new PartitionOccupyingTowerList();
final short width;
final short height;
private final Player[] players;
private final IBlockingProvider blockingProvider;
private final Team[] teams;
final short[] partitions;
private final byte[] towers;
Partition[] partitionObjects = new Partition[NUMBER_OF_START_PARTITION_OBJECTS];
private final short[] blockedPartitionsForPlayers;
private transient Object partitionsWriteLock;
private transient IPlayerChangedListener playerChangedListener = IPlayerChangedListener.DEFAULT_IMPLEMENTATION;
public PartitionsGrid(short width, short height, PlayerSetting[] playerSettings, IBlockingProvider blockingProvider) {
this.width = width;
this.height = height;
this.blockingProvider = blockingProvider;
this.players = new Player[playerSettings.length]; // create the players.
this.blockedPartitionsForPlayers = new short[playerSettings.length];
Map<Byte, Team> teams = new HashMap<>();
for (byte playerId = 0; playerId < playerSettings.length; playerId++) {
PlayerSetting playerSetting = playerSettings[(int) playerId];
if (playerSetting.isAvailable()) {
if (teams.get(playerSetting.getTeamId()) == null) {
teams.put(playerSetting.getTeamId(), new Team(playerSetting.getTeamId()));
}
Team team = teams.get(playerSetting.getTeamId());
this.players[playerId] = new Player(playerId, team, (byte) playerSettings.length, playerSetting.getPlayerType(),
playerSetting.getCivilisation());
team.registerPlayer(this.players[playerId]);
this.blockedPartitionsForPlayers[playerId] = createNewPartition(playerId); // create a blocked partition for every player
}
}
Team[] teamsArray = new Team[teams.size()];
this.teams = teams.values().toArray(teamsArray);
this.partitions = new short[width * height];
this.towers = new byte[width * height];
// the no player partition (the manager won't be started)
this.partitionObjects[NO_PLAYER_PARTITION_ID] = new Partition(NO_PLAYER_PARTITION_ID, (byte) -1, width * height);
initAdditionalFields();
}
public void initWithPlayerSettings(PlayerSetting[] playerSettings) {
for (int i = 0; i < players.length; i++) {
if (players[i] != null && playerSettings[i].isAvailable()) {
players[i].setPlayerType(playerSettings[i].getPlayerType());
players[i].setCivilisation(playerSettings[i].getCivilisation());
}
}
}
public short getWidth() {
return width;
}
public short getHeight() {
return height;
}
private void writeObject(ObjectOutputStream oos) throws IOException {
int normalizedPartitions = checkNormalizePartitions(0);
System.out.println("Normalized " + normalizedPartitions + " partitions");
oos.defaultWriteObject();
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
initAdditionalFields();
}
private void initAdditionalFields() {
partitionsWriteLock = new Object();
}
public boolean isDefaultPartition(short partitionId) {
return partitionId == NO_PLAYER_PARTITION_ID;
}
public short getPartitionIdAt(int x, int y) {
return partitionObjects[partitions[x + y * width]].partitionId;
}
/**
* ONLY FOR TESTING: <br>
* This method gives the currently set partition at the given partition and not their representative. <br>
* Therefore it can be used for viewing the unmerged partitions.
*
* @param x
* @param y
* @return
*/
public short getRealPartitionIdAt(int x, int y) {
return partitions[x + y * width];
}
public Partition getPartitionAt(int x, int y) {
return partitionObjects[partitions[x + y * width]];
}
public MaterialProductionSettings getMaterialProductionAt(int x, int y) {
return getPartitionAt(x, y).getMaterialProduction();
}
public PartitionManager getPartitionAt(ILocatable locatable) {
ShortPoint2D pos = locatable.getPos();
return getPartitionAt(pos.x, pos.y);
}
public byte getPlayerIdAt(int x, int y) {
return getPartitionAt(x, y).playerId;
}
public Player[] getPlayers() {
return players;
}
public Player getPlayer(int playerId) {
return players[playerId];
}
public Player getPlayerAt(int x, int y) {
short playerId = partitionObjects[partitions[x + y * width]].playerId;
if (playerId >= 0) {
return players[playerId];
} else {
return null;
}
}
public boolean ownsPlayerPartition(short partitionId, byte playerId) {
return partitionObjects[partitionId].playerId == playerId;
}
public byte getTowerCountAt(int x, int y) {
return towers[x + y * width];
}
public boolean isEnforcedByTower(int x, int y) {
return towers[x + y * width] > 0;
}
/**
* Occupies the given area for the given playerId if it is not already occupied by towers of an enemy.
*
* @param playerId
* The id of the occupying player.
* @param influencingArea
* The area affected by the tower.
* @param groundArea
* The ground area of the tower.
*/
public void addTowerAndOccupyArea(byte playerId, MapCircle influencingArea, FreeMapArea groundArea) {
IMapArea filteredArea = new MapShapeFilter(influencingArea, width, height);
PartitionOccupyingTower tower = new PartitionOccupyingTower(playerId, influencingArea.getCenter(), groundArea, filteredArea,
influencingArea.getBorders(), (int) influencingArea.getRadius());
occupyAreaOfTower(tower);
}
/**
* Removes the tower at the given position from the grid.
*
* @param pos
* The position of the tower.
* @return
*/
public CoordinateStream removeTowerAndFreeOccupiedArea(ShortPoint2D pos) {
// get the tower object and the informations of it.
PartitionOccupyingTower tower = occupyingTowers.removeAt(pos);
if (tower == null) {
return CoordinateStream.EMPTY;
}
// reduce the tower counter
CoordinateStream towerStream = tower.area.stream();
changeTowerCounter(tower.playerId, towerStream, -1);
checkOtherTowersInArea(tower);
return towerStream;
}
/**
* Changes the player of the tower at given position to the new player. After this operation, the given ground area will always be occupied by the
* new player.
*
* @param towerPosition
* @param newPlayerId
* @return
*/
public CoordinateStream changePlayerOfTower(ShortPoint2D towerPosition, byte newPlayerId) {
// get the tower object and the information of it.
PartitionOccupyingTower tower = occupyingTowers.removeAt(towerPosition);
if (tower == null) {
return CoordinateStream.EMPTY; // return if no tower has been found
}
// reduce the tower counter
changeTowerCounter(tower.playerId, tower.getAreaWithoutGround(), -1);
// let the other towers occupy the area
checkOtherTowersInArea(tower);
PartitionOccupyingTower newTower = new PartitionOccupyingTower(newPlayerId, tower);
occupyAreaOfTower(newTower);
return newTower.area.stream();
}
private void occupyAreaOfTower(PartitionOccupyingTower tower) {
// set the tower counter of the groundArea to 0 => the ground area will be occupied
tower.groundArea.stream().forEach((x, y) -> towers[x + y * width] = 0);
// occupy the area for the new player
occupyAreaByTower(tower.playerId, tower.area.stream(), tower.areaBorders);
occupyingTowers.add(tower);
// recalculate the tower counter for the ground area
recalculateTowerCounter(tower, tower.groundArea);
}
public void changePlayerAt(ShortPoint2D position, byte playerId) {
int idx = position.x + position.y * width;
if (towers[idx] <= 0) {
short newPartition = createNewPartition(playerId);
changePartitionUncheckedAt(position.x, position.y, newPartition);
notifyPlayerChangedListener(position.x, position.y, playerId);
PartitionsListingBorderVisitor borderVisitor = new PartitionsListingBorderVisitor(this, blockingProvider);
// visit the direct neighbors of the position
for (EDirection currDir : EDirection.VALUES) {
borderVisitor.visit(position.x, position.y, currDir.gridDeltaX + position.x, currDir.gridDeltaY + position.y);
}
checkMergesAndDividesOnPartitionsList(playerId, newPartition, borderVisitor.getPartitionsList());
}
}
/**
* Recalculates the tower counter for the given area. <br>
* NOTE: The given area must completely belong to the given player!
*
* @param tower
* @param tower
* @param area
*/
private void recalculateTowerCounter(PartitionOccupyingTower tower, IMapArea area) {
area.stream().forEach((x, y) -> towers[x + y * width] = 0);
List<Tuple<Integer, PartitionOccupyingTower>> towersInRange = occupyingTowers.getTowersInRange(tower.position, tower.radius,
currTower -> currTower.playerId == tower.playerId);
stream(towersInRange)
.forEach(currTower -> area.stream()
.filter(currTower.e2.area::contains)
.forEach((x, y) -> towers[x + y * width]++));
}
/**
* Checks if other towers that intersect the area of the given tower can occupy free positions of the area of the given tower and lets them do so.
*
* @param tower
*/
private void checkOtherTowersInArea(PartitionOccupyingTower tower) {
// Get the positions that may change their owner (tower counter <= 0)
// Save these positions in the list because the list must not change during the loop over the other towers
CoordinateStream freedPositions = tower.area.stream().filter((x, y) -> towers[x + y * width] <= 0).freeze();
// if at least one position may change the player
// check if other towers occupy the area
if (!freedPositions.isEmpty()) {
List<Tuple<Integer, PartitionOccupyingTower>> towersInRange = occupyingTowers.getTowersInRange(tower.position,
tower.radius, currTower -> currTower.playerId != tower.playerId);
// sort the towers by their distance to the removed tower
Lists.sort(towersInRange, Tuple.getE1Comparator());
for (Tuple<Integer, PartitionOccupyingTower> curr : towersInRange) {
final PartitionOccupyingTower currTower = curr.e2;
final IMapArea currArea = currTower.area;
CoordinateStream area = freedPositions.filter(currArea::contains);
occupyAreaByTower(currTower.playerId, area, currTower.areaBorders);
PartitionsListingBorderVisitor borderVisitor = new PartitionsListingBorderVisitor(this, blockingProvider);
final FreeMapArea groundArea = currTower.groundArea;
ShortPoint2D upperLeftGroundAreaPosition = groundArea.getUpperLeftPosition();
BorderTraversingAlgorithm.traverseBorder(groundArea::contains, upperLeftGroundAreaPosition, borderVisitor, true);
checkMergesAndDividesOnPartitionsList(currTower.playerId,
getPartitionIdAt(upperLeftGroundAreaPosition.x, upperLeftGroundAreaPosition.y), borderVisitor.getPartitionsList());
}
}
}
/**
* Occupies the given area for the given playerId.
*
* @param playerId
* @param influencingArea
* @param borders
*/
private void occupyAreaByTower(final byte playerId, CoordinateStream influencingArea, SRectangle borders) {
CoordinateStream filtered = influencingArea.filter((x, y) -> {
int index = x + y * width;
return towers[index] <= 0 && partitionObjects[partitions[index]].playerId != playerId;
}).freeze();
// create PartitionCalculator
PartitionCalculatorAlgorithm partitioner = new PartitionCalculatorAlgorithm(filtered, blockingProvider, borders.xMin, borders.yMin,
borders.xMax, borders.yMax);
partitioner.calculatePartitions();
// take over the positions
short[] newPartitionsMap = acquirePartitionedArea(playerId, partitioner);
// check for needed merges
checkForMergesAndDivides(playerId, partitioner, newPartitionsMap);
// increase the tower counter
changeTowerCounter(playerId, influencingArea, +1);
filtered.forEach((x, y) -> notifyPlayerChangedListener(x, y, playerId));
}
private void checkForMergesAndDivides(byte playerId, PartitionCalculatorAlgorithm partitioner, short[] newPartitionsMap) {
for (int i = PartitionCalculatorAlgorithm.NUMBER_OF_RESERVED_PARTITIONS; i < partitioner.getNumberOfPartitions(); i++) {
// traverse the border of the partition and collect the partitions around the partition
PartitionsListingBorderVisitor borderVisitor = new PartitionsListingBorderVisitor(this, blockingProvider);
ShortPoint2D pos = partitioner.getPartitionBorderPos(i);
short innerPartition = newPartitionsMap[i];
BorderTraversingAlgorithm.traverseBorder((x, y) -> partitionObjects[partitions[x + y * width]] == partitionObjects[innerPartition], pos,
borderVisitor, true);
checkMergesAndDividesOnPartitionsList(playerId, innerPartition, borderVisitor.getPartitionsList());
}
}
private void checkMergesAndDividesOnPartitionsList(byte playerId, final short innerPartition, LinkedList<BorderPartitionInfo> partitionsList) {
if (partitionsList.isEmpty()) {
return; // nothing to do
}
// check for divides
HashMap<Short, BorderPartitionInfo> foundPartitionsSet = new HashMap<>();
for (BorderPartitionInfo currPartitionInfo : partitionsList) {
Short currPartitionId = currPartitionInfo.partitionId;
BorderPartitionInfo existingPartitionInfo = foundPartitionsSet.get(currPartitionId);
if (existingPartitionInfo != null) {
if (partitionObjects[currPartitionId].playerId != playerId) { // the player cannot divide its own partitions => only check other
// player's positions
checkIfDividePartition(currPartitionInfo, existingPartitionInfo);
// if the entry of the set changed its partition, replace that entry with the one of the old partition. Further divides can only
// happen with partitions which also have currPartitionId.
short newPartitionIdOfExistingPartition = getPartitionIdAt(existingPartitionInfo.positionOfPartition.x,
existingPartitionInfo.positionOfPartition.y);
if (newPartitionIdOfExistingPartition != currPartitionId) {
foundPartitionsSet.put(currPartitionId, currPartitionInfo);
}
}
} else {
foundPartitionsSet.put(currPartitionId, currPartitionInfo);
}
}
// check if partitions need to be merged
partitionsList.addLast(partitionsList.getFirst()); // add first at the end
for (BorderPartitionInfo currPartition : partitionsList) {
Partition currPartitionObject = partitionObjects[currPartition.partitionId];
if (currPartitionObject.playerId == playerId && partitionObjects[currPartition.partitionId] != partitionObjects[innerPartition]) {
mergePartitions(currPartition.partitionId, innerPartition);
}
}
}
/**
* Checks if the given partitions is divided and the both given positions are on separated parts of the partition.
*
* @param partitionInfo1
* @param partitionInfo2
*/
private void checkIfDividePartition(BorderPartitionInfo partitionInfo1, BorderPartitionInfo partitionInfo2) {
assert partitionInfo1.partitionId == partitionInfo2.partitionId;
final short partition = partitionInfo1.partitionId;
MutableInt partition1Size = new MutableInt();
MutableInt partition2Size = new MutableInt();
if (partition != NO_PLAYER_PARTITION_ID
&& PartitionsDividedTester.isPartitionDivided(partitionObjects, partitions, width, partitionInfo1, partition1Size, partitionInfo2,
partition2Size)) {
if (partition1Size.value < partition2Size.value) {
dividePartition(partition, partitionInfo1.positionOfPartition, partitionInfo2.positionOfPartition);
} else {
dividePartition(partition, partitionInfo2.positionOfPartition, partitionInfo1.positionOfPartition);
}
}
}
private short[] acquirePartitionedArea(byte playerId, PartitionCalculatorAlgorithm partitioner) {
int numberOfNewPartitions = partitioner.getNumberOfPartitions();
short[] newPartitionsMap = new short[numberOfNewPartitions];
newPartitionsMap[PartitionCalculatorAlgorithm.BLOCKED_PARTITION] = blockedPartitionsForPlayers[playerId];
for (int i = PartitionCalculatorAlgorithm.NUMBER_OF_RESERVED_PARTITIONS; i < numberOfNewPartitions; i++) {
newPartitionsMap[i] = createNewPartition(playerId);
}
int minX = partitioner.getMinX();
int minY = partitioner.getMinY();
int width = partitioner.getWidth();
int height = partitioner.getHeight();
for (short dY = 0; dY < height; dY++) {
for (int dX = 0; dX < width; dX++) {
short partition = partitioner.getPartitionAt(dX, dY);
if (partition != PartitionCalculatorAlgorithm.NO_PARTITION) {
short x = (short) (dX + minX);
short y = (short) (dY + minY);
// Set the new partitions and take over goods and so on
changePartitionUncheckedAt(x, y, newPartitionsMap[partition]);
}
}
}
return newPartitionsMap;
}
private void changeTowerCounter(final byte playerId, CoordinateStream influencingArea, int delta) {
influencingArea.filter((x, y) -> partitionObjects[partitions[x + y * width]].playerId == playerId)
.forEach((x, y) -> towers[x + y * width] += delta);
}
/**
* Merges two partitions. The smaller partition is merged into the bigger one.
*
* @param partition1
* @param partition2
* @return The resulting partition
*/
short mergePartitions(short partition1, short partition2) {
short biggerPartition;
short smallerPartition;
if (partitionObjects[partition1].getNumberOfElements() >= partitionObjects[partition2].getNumberOfElements()) {
biggerPartition = partition1;
smallerPartition = partition2;
} else {
biggerPartition = partition2;
smallerPartition = partition1;
}
biggerPartition = partitionObjects[biggerPartition].partitionId; // ensure that we have the top representative
smallerPartition = partitionObjects[smallerPartition].partitionId;
assert biggerPartition != smallerPartition : "the partitions can not be the same!";
Partition biggerPartitionObject = partitionObjects[biggerPartition];
Partition smallerPartitionObject = partitionObjects[smallerPartition];
if (biggerPartitionObject.playerId != smallerPartitionObject.playerId) {
System.err.println("ERROR: Merging partitions of different players!!!");
} else if (biggerPartition == blockedPartitionsForPlayers[biggerPartitionObject.playerId]
|| smallerPartition == blockedPartitionsForPlayers[smallerPartitionObject.playerId]) {
System.err.println("ERROR: Merging blocked partition!!!");
}
smallerPartitionObject.mergeInto(biggerPartitionObject);
smallerPartitionObject.stopManager();
partitionObjects[smallerPartition] = biggerPartitionObject;
/**
* Flatten all hierarchies: <br>
* start situation: 1 <- 2 and 3 <- 4 <br>
* merge of 4 and 2 leads to a merge of 1 and 3. Say 1 is the resulting partition. Then we get: <br>
* 1 <- 2, 1 <- 3 <- 4 <br>
* The chain 1 <- 3 <- 4 will be cut to 1 <- 3 and 1 <- 4 by the following code. <br>
*/
int numberOfPartitions = partitionObjects.length;
for (int i = 1; i < numberOfPartitions; i++) {
if (partitionObjects[i] == smallerPartitionObject) {
partitionObjects[i] = biggerPartitionObject;
}
}
return biggerPartition;
}
/**
* Divides the given partition. The both positions must be on the border of the given partition and be on the parts that are now distinct and
* shall be divided. NOTE: There will be no check if the partition is really divided!
*
* @param oldPartition
* The original partition that now needs to be divided.
* @param relabelStartPosition
* A position in the part of the divided partition that will be relabeled.
* @param otherPosition
* A position in the part of the divided partition that will keep the old partition id.
*/
private void dividePartition(final short oldPartition, ShortPoint2D relabelStartPosition, ShortPoint2D otherPosition) {
if (oldPartition == NO_PLAYER_PARTITION_ID) {
return; // don't divide the no player partition
}
Partition partitionObject = partitionObjects[oldPartition];
System.out.println("Dividing partition " + oldPartition + " with relabelStartPos: " + relabelStartPosition + " and "
+ partitionObject.getNumberOfElements() + " elements. " + otherPosition + " will keep the old partition id.");
short newPartition = createNewPartition(partitionObject.playerId);
relabelArea(oldPartition, relabelStartPosition, newPartition);
}
/**
* Relabels all of the given old partition connected to the start position to the new partition.
*
* @param oldPartition
* The id of the old partition.
* @param relabelStartPos
* The start position of the relabeling. Only positions connected to this position by other positions of the old partition will be
* relabeled.
* @param newPartition
* The id of the new partition.
*/
private void relabelArea(final short oldPartition, ShortPoint2D relabelStartPos, final short newPartition) {
// relabel the partition
IContainingProvider containingProvider = (x, y) -> partitionObjects[partitions[x + y * width]].partitionId == oldPartition;
IAreaVisitor relabelAreaVisitor = (x, y) -> {
changePartitionUncheckedAt(x, y, newPartition);
return true;
};
AreaTraversingAlgorithm.traverseArea(containingProvider, relabelAreaVisitor, relabelStartPos, width, height);
}
/**
* Changes the partition at the given position to the given new partition. <br>
* NOTE: There will be no checks if the new partition exists or if this change divides an other partition or should lead to a merge.
*
* @param x
* x coordinate of the position.
* @param y
* y coordinate of the position.
* @param newPartition
* The new partition that will be set at the given partition.
* @return the player id of the new partition.
*/
byte changePartitionUncheckedAt(int x, int y, short newPartition) {
int idx = x + y * width;
Partition oldPartitionObject = partitionObjects[partitions[idx]];
Partition newPartitionObject = partitionObjects[newPartition];
oldPartitionObject.removePositionTo(x, y, newPartitionObject);
synchronized (partitionsWriteLock) {
partitions[idx] = newPartition;
}
return newPartitionObject.playerId;
}
private void notifyPlayerChangedListener(int x, int y, byte newPlayer) {
playerChangedListener.playerChangedAt(x, y, newPlayer);
}
short createNewPartition(byte playerId) { // package private for tests
checkNormalizePartitions(NUMBER_OF_START_PARTITION_OBJECTS / 2);
short newPartitionId = 1;
while (partitionObjects[newPartitionId] != null) { // get a free partition
newPartitionId++;
int length = partitionObjects.length;
if (newPartitionId >= length) {
synchronized (partitionsWriteLock) {
int newLength = (int) (length * PARTITIONS_EXPAND_FACTOR);
Partition[] newPartitionObjects = new Partition[newLength];
System.arraycopy(partitionObjects, 0, newPartitionObjects, 0, length);
partitionObjects = newPartitionObjects;
System.out.println("PartitionsGrid: Expanded the number of possible partitions from " + length + " to " + newLength);
}
}
}
Partition newPartitionObject = new Partition(newPartitionId, playerId, players[playerId]);
newPartitionObject.startManager();
partitionObjects[newPartitionId] = newPartitionObject;
return newPartitionId;
}
public void setPartitionAt(int x, int y, short newPartition) {
if (getPartitionAt(x, y) != partitionObjects[newPartition]) {
byte playerId = changePartitionUncheckedAt(x, y, newPartition);
notifyPlayerChangedListener(x, y, playerId);
}
}
/**
* Sets the given listener. The listener will then be informed of any positions that change their player.
*
* @param listener
* The listener to be set or null if no listener should be set.
*/
public void setPlayerChangedListener(IPlayerChangedListener listener) {
if (listener == null) {
this.playerChangedListener = IPlayerChangedListener.DEFAULT_IMPLEMENTATION;
} else {
this.playerChangedListener = listener;
}
}
public byte getNumberOfPlayers() {
return (byte) players.length;
}
private int checkNormalizePartitions(int mergePartitionsThreshold) {
int maxPartitions = this.partitionObjects.length;
BitSet stoppedManagers = new BitSet(maxPartitions);
int counter = 0;
for (int i = 1; i < maxPartitions; i++) {
PartitionManager partitionObject = this.partitionObjects[i];
if (partitionObject != null && this.partitionObjects[i].partitionId != i) {
stoppedManagers.set(i);
counter++;
}
}
if (counter <= mergePartitionsThreshold) {
return 0;// skip the rest if nothing is to do.
}
// normalize the partitions
for (int y = 0; y < height; y++) {
synchronized (partitionsWriteLock) { // the lock is acquired here to prevent holding it for a long time without requesting it every time
for (int x = 0; x < width; x++) {
int idx = x + y * width;
this.partitions[idx] = this.partitionObjects[this.partitions[idx]].partitionId;
}
}
}
// clear the partition objects
synchronized (partitionsWriteLock) {
for (int i = 1; i < maxPartitions; i++) {
if (stoppedManagers.get(i)) {
this.partitionObjects[i] = null;
}
}
}
return counter;
}
public IPartitionData getPartitionDataForManagerAt(int x, int y) {
return getPartitionAt(x, y).getPartitionData();
}
public Team[] getTeams() {
return teams;
}
public PartitionManagerSettings getPartitionSettings(ShortPoint2D position) {
return getPartitionSettings(position.x, position.y);
}
public PartitionManagerSettings getPartitionSettings(int x, int y) {
return getPartitionAt(x, y).getPartitionSettings();
}
}