/******************************************************************************* * 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.ai.highlevel; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Vector; import java8.util.Comparators; import jsettlers.ai.highlevel.AiPositions.AiPositionFilter; import jsettlers.algorithms.construction.AbstractConstructionMarkableMap; import jsettlers.common.CommonConstants; import jsettlers.common.buildings.EBuildingType; import jsettlers.common.buildings.IMaterialProductionSettings; import jsettlers.common.landscape.ELandscapeType; import jsettlers.common.landscape.EResourceType; import jsettlers.common.map.partition.IPartitionData; import jsettlers.common.mapobject.EMapObjectType; import jsettlers.common.material.EMaterialType; import jsettlers.common.movable.EDirection; import jsettlers.common.movable.EMovableType; import jsettlers.common.movable.IMovable; import jsettlers.common.position.RelativePoint; import jsettlers.common.position.ShortPoint2D; import jsettlers.logic.buildings.Building; import jsettlers.logic.buildings.WorkAreaBuilding; import jsettlers.logic.map.grid.MainGrid; import jsettlers.logic.map.grid.flags.FlagsGrid; import jsettlers.logic.map.grid.landscape.LandscapeGrid; import jsettlers.logic.map.grid.movable.MovableGrid; import jsettlers.logic.map.grid.objects.AbstractHexMapObject; import jsettlers.logic.map.grid.objects.ObjectsGrid; import jsettlers.logic.map.grid.partition.PartitionsGrid; import jsettlers.logic.movable.interfaces.ILogicMovable; import jsettlers.logic.player.Player; import jsettlers.logic.player.Team; import static jsettlers.common.buildings.EBuildingType.BIG_TOWER; import static jsettlers.common.buildings.EBuildingType.CASTLE; import static jsettlers.common.buildings.EBuildingType.LUMBERJACK; import static jsettlers.common.buildings.EBuildingType.TOWER; import static jsettlers.common.mapobject.EMapObjectType.STONE; import static jsettlers.common.mapobject.EMapObjectType.TREE_ADULT; import static jsettlers.common.mapobject.EMapObjectType.TREE_GROWING; import static jsettlers.common.movable.EMovableType.SWORDSMAN_L1; import static jsettlers.common.movable.EMovableType.SWORDSMAN_L2; import static jsettlers.common.movable.EMovableType.SWORDSMAN_L3; /** * This class calculates statistics based on the grids which are used by highlevel and lowlevel KI. The statistics are calculated once and read * multiple times within one AiExecutor step triggerd by the game clock. * * @author codingberlin */ public class AiStatistics { private static final EBuildingType[] REFERENCE_POINT_FINDER_BUILDING_ORDER = {LUMBERJACK, TOWER, BIG_TOWER, CASTLE}; public static final int NEAR_STONE_DISTANCE = 5; private final Queue<Building> buildings; private final PlayerStatistic[] playerStatistics; private final Map<EMapObjectType, AiPositions> sortedCuttableObjectsInDefaultPartition; private final AiPositions[] sortedResourceTypes; private final AiPositions sortedRiversInDefaultPartition; private final MainGrid mainGrid; private final LandscapeGrid landscapeGrid; private final ObjectsGrid objectsGrid; private final PartitionsGrid partitionsGrid; private final MovableGrid movableGrid; private final FlagsGrid flagsGrid; private final AbstractConstructionMarkableMap constructionMarksGrid; private final AiMapInformation aiMapInformation; private final long[] resourceCountInDefaultPartition; public AiStatistics(MainGrid mainGrid) { this.buildings = Building.getAllBuildings(); this.mainGrid = mainGrid; this.landscapeGrid = mainGrid.getLandscapeGrid(); this.objectsGrid = mainGrid.getObjectsGrid(); this.partitionsGrid = mainGrid.getPartitionsGrid(); this.movableGrid = mainGrid.getMovableGrid(); this.flagsGrid = mainGrid.getFlagsGrid(); this.constructionMarksGrid = mainGrid.getConstructionMarksGrid(); this.playerStatistics = new PlayerStatistic[mainGrid.getGuiInputGrid().getNumberOfPlayers()]; this.aiMapInformation = new AiMapInformation(partitionsGrid, landscapeGrid); ; for (byte i = 0; i < mainGrid.getGuiInputGrid().getNumberOfPlayers(); i++) { this.playerStatistics[i] = new PlayerStatistic(); } sortedRiversInDefaultPartition = new AiPositions(); sortedCuttableObjectsInDefaultPartition = new HashMap<EMapObjectType, AiPositions>(); sortedResourceTypes = new AiPositions[EResourceType.VALUES.length]; for (int i = 0; i < sortedResourceTypes.length; i++) { sortedResourceTypes[i] = new AiPositions(); } resourceCountInDefaultPartition = new long[EResourceType.VALUES.length]; } public byte getFlatternEffortAtPositionForBuilding(final ShortPoint2D position, final EBuildingType buildingType) { byte flattenEffort = constructionMarksGrid.calculateConstructionMarkValue(position.x, position.y, buildingType.getProtectedTiles()); if (flattenEffort == -1) { return Byte.MAX_VALUE; } return flattenEffort; } public void updateStatistics() { for (PlayerStatistic playerStatistic : playerStatistics) { playerStatistic.clearAll(); } sortedRiversInDefaultPartition.clear(); sortedCuttableObjectsInDefaultPartition.clear(); for (AiPositions xCoordinatesMap : sortedResourceTypes) { xCoordinatesMap.clear(); } updateBuildingStatistics(); updateMapStatistics(); } private void updateBuildingStatistics() { for (Building building : buildings) { PlayerStatistic playerStatistic = playerStatistics[building.getPlayerId()]; EBuildingType type = building.getBuildingType(); updateNumberOfNotFinishedBuildings(playerStatistic, building); updateBuildingsNumbers(playerStatistic, building, type); updateBuildingPositions(playerStatistic, type, building); } } private void updateBuildingPositions(PlayerStatistic playerStatistic, EBuildingType type, Building building) { if (!playerStatistic.buildingPositions.containsKey(type)) { playerStatistic.buildingPositions.put(type, new ArrayList<ShortPoint2D>()); } playerStatistic.buildingPositions.get(type).add(building.getPos()); if (type == EBuildingType.WINEGROWER) { playerStatistic.wineGrowerWorkAreas.add(((WorkAreaBuilding) building).getWorkAreaCenter()); } else if (type == EBuildingType.FARM) { playerStatistic.farmWorkAreas.add(((WorkAreaBuilding) building).getWorkAreaCenter()); } } private void updateBuildingsNumbers(PlayerStatistic playerStatistic, Building building, EBuildingType type) { playerStatistic.totalBuildingsNumbers[type.ordinal]++; if (building.getStateProgress() == 1f) { playerStatistic.buildingsNumbers[type.ordinal]++; } } private void updateNumberOfNotFinishedBuildings(PlayerStatistic playerStatistic, Building building) { playerStatistic.numberOfTotalBuildings++; if (building.getStateProgress() < 1f) { playerStatistic.numberOfNotFinishedBuildings++; if (building.getBuildingType().isMilitaryBuilding()) { playerStatistic.numberOfNotOccupiedMilitaryBuildings++; } } else if (building.getBuildingType().isMilitaryBuilding()) { if (building.isOccupied()) { playerStatistic.isAlive = true; } else { playerStatistic.numberOfNotOccupiedMilitaryBuildings++; } } } private void updateMapStatistics() { aiMapInformation.clear(); updatePartitionIdsToBuildOn(); short width = mainGrid.getWidth(); short height = mainGrid.getHeight(); Arrays.fill(resourceCountInDefaultPartition, 0); for (short x = 0; x < width; x++) { for (short y = 0; y < height; y++) { Player player = partitionsGrid.getPlayerAt(x, y); int mapInformationPlayerId; if (player != null) { mapInformationPlayerId = player.playerId; } else { mapInformationPlayerId = aiMapInformation.resourceAndGrassCount.length - 1; } if (landscapeGrid.getResourceAmountAt(x, y) > 0) { EResourceType resourceType = landscapeGrid.getResourceTypeAt(x, y); sortedResourceTypes[resourceType.ordinal].addNoCollission(x, y); if (resourceType != EResourceType.FISH) { aiMapInformation.resourceAndGrassCount[mapInformationPlayerId][resourceType.ordinal]++; if (player != null) { playerStatistics[player.playerId].resourceCount[resourceType.ordinal]++; } else { resourceCountInDefaultPartition[resourceType.ordinal]++; } } else if (landscapeGrid.getLandscapeTypeAt(x, y) == ELandscapeType.WATER1) { int fishMapInformationPlayerId = mapInformationPlayerId; if (mapInformationPlayerId == aiMapInformation.resourceAndGrassCount.length - 1) { fishMapInformationPlayerId = mapInformationPlayerIdOfPosition((short) (x + 3), y); if (fishMapInformationPlayerId == aiMapInformation.resourceAndGrassCount.length - 1) { fishMapInformationPlayerId = mapInformationPlayerIdOfPosition((short) (x - 3), y); if (fishMapInformationPlayerId == aiMapInformation.resourceAndGrassCount.length - 1) { fishMapInformationPlayerId = mapInformationPlayerIdOfPosition(x, (short) (y + 3)); if (fishMapInformationPlayerId == aiMapInformation.resourceAndGrassCount.length - 1) { fishMapInformationPlayerId = mapInformationPlayerIdOfPosition(x, (short) (y - 3)); } } } } aiMapInformation.resourceAndGrassCount[fishMapInformationPlayerId][resourceType.ordinal]++; if (fishMapInformationPlayerId == aiMapInformation.resourceAndGrassCount.length - 1) { resourceCountInDefaultPartition[resourceType.ordinal]++; } else { playerStatistics[fishMapInformationPlayerId].resourceCount[resourceType.ordinal]++; } } } if (landscapeGrid.getLandscapeTypeAt(x, y).isGrass()) { aiMapInformation.resourceAndGrassCount[mapInformationPlayerId][aiMapInformation.GRASS_INDEX]++; } ILogicMovable movable = movableGrid.getMovableAt(x, y); if (movable != null) { byte movablePlayerId = movable.getPlayerId(); PlayerStatistic movablePlayerStatistic = playerStatistics[movablePlayerId]; EMovableType movableType = movable.getMovableType(); if (!movablePlayerStatistic.movablePositions.containsKey(movableType)) { movablePlayerStatistic.movablePositions.put(movableType, new Vector<ShortPoint2D>()); } movablePlayerStatistic.movablePositions.get(movableType).add(movable.getPos()); if (player != null && player.playerId != movablePlayerId && movableType.isSoldier() && getEnemiesOf(player.playerId).contains(movablePlayerId)) { playerStatistics[player.playerId].enemyTroopsInTown.addNoCollission(movable.getPos().x, movable.getPos().y); } } if (player == null) { updateFreeLand(x, y); } else if (partitionsGrid.getPartitionIdAt(x, y) == playerStatistics[player.playerId].partitionIdToBuildOn) { updatePlayerLand(x, y, player); } if (player != null && isBorderOf(x, y, player.playerId)) { if (partitionsGrid.getPartitionIdAt(x, y) == playerStatistics[player.playerId].partitionIdToBuildOn) { playerStatistics[player.playerId].border.add(x, y); } else { playerStatistics[player.playerId].otherPartitionBorder.add(x, y); } } } } } private int mapInformationPlayerIdOfPosition(short x, short y) { if (!mainGrid.isInBounds(x, y)) { return aiMapInformation.resourceAndGrassCount.length - 1; } byte playerId = mainGrid.getPartitionsGrid().getPlayerIdAt(x, y); if (playerId == -1) { return aiMapInformation.resourceAndGrassCount.length - 1; } return playerId; } private boolean isBorderOf(int x, int y, byte playerId) { return isIngestibleBy(x + 1, y + 1, playerId) || isIngestibleBy(x + 1, y - 1, playerId) || isIngestibleBy(x - 1, y + 1, playerId) || isIngestibleBy(x - 1, y - 1, playerId); } private boolean isIngestibleBy(int x, int y, byte playerId) { return mainGrid.isInBounds(x, y) && partitionsGrid.getPlayerIdAt(x, y) != playerId && !mainGrid.getLandscapeGrid().getLandscapeTypeAt(x, y).isBlocking && !partitionsGrid.isEnforcedByTower(x, y); } private void updatePlayerLand(short x, short y, Player player) { byte playerId = player.playerId; PlayerStatistic playerStatistic = playerStatistics[playerId]; if (mainGrid.getFlagsGrid().isProtected(x, y)) { AbstractHexMapObject o = objectsGrid.getObjectsAt(x, y); if (o != null) { if (o.hasCuttableObject(STONE) && isCuttableByPlayer(x, y, player.playerId)) { playerStatistic.stones.addNoCollission(x, y); } else if (o.hasMapObjectTypes(TREE_GROWING, TREE_ADULT) && isCuttableByPlayer(x, y, player.playerId)) { playerStatistic.trees.addNoCollission(x, y); } } } else { playerStatistic.landToBuildOn.addNoCollission(x, y); } ELandscapeType landscape = landscapeGrid.getLandscapeTypeAt(x, y); if (landscape.isRiver()) { playerStatistic.rivers.addNoCollission(x, y); } if (objectsGrid.hasMapObjectType(x, y, EMapObjectType.WINE_GROWING, EMapObjectType.WINE_HARVESTABLE)) { playerStatistic.wineCount++; } } private boolean isCuttableByPlayer(short x, short y, byte playerId) { byte[] playerIds = new byte[4]; playerIds[0] = partitionsGrid.getPlayerIdAt(x - 2, y - 2); playerIds[1] = partitionsGrid.getPlayerIdAt(x - 2, y + 2); playerIds[2] = partitionsGrid.getPlayerIdAt(x + 2, y - 2); playerIds[3] = partitionsGrid.getPlayerIdAt(x + 2, y + 2); for (byte positionPlayerId : playerIds) { if (positionPlayerId != playerId) { return false; } } return true; } private void updateFreeLand(short x, short y) { if (objectsGrid.hasCuttableObject(x, y, TREE_ADULT)) { AiPositions trees = sortedCuttableObjectsInDefaultPartition.get(TREE_ADULT); if (trees == null) { trees = new AiPositions(); sortedCuttableObjectsInDefaultPartition.put(TREE_ADULT, trees); } trees.addNoCollission(x, y); } if (objectsGrid.hasCuttableObject(x, y, STONE)) { AiPositions stones = sortedCuttableObjectsInDefaultPartition.get(STONE); if (stones == null) { stones = new AiPositions(); sortedCuttableObjectsInDefaultPartition.put(STONE, stones); } stones.addNoCollission(x, y); updateNearStones(x, y); } ELandscapeType landscape = landscapeGrid.getLandscapeTypeAt(x, y); if (landscape.isRiver()) { sortedRiversInDefaultPartition.addNoCollission(x, y); } } private void updateNearStones(short x, short y) { for (EDirection dir : EDirection.VALUES) { int currX = dir.getNextTileX(x, NEAR_STONE_DISTANCE); int currY = dir.getNextTileY(y, NEAR_STONE_DISTANCE); if (mainGrid.isInBounds(currX, currY)) { byte playerId = partitionsGrid.getPlayerIdAt(currX, currY); if (playerId != -1 && hasPlayersBlockedPartition(playerId, x, y)) { playerStatistics[playerId].stonesNearBy.addNoCollission(x, y); } } } } private void updatePartitionIdsToBuildOn() { for (byte playerId = 0; playerId < playerStatistics.length; playerId++) { ShortPoint2D referencePosition = null; for (EBuildingType referenceFinderBuildingType : REFERENCE_POINT_FINDER_BUILDING_ORDER) { if (getTotalNumberOfBuildingTypeForPlayer(referenceFinderBuildingType, playerId) > 0) { referencePosition = getBuildingPositionsOfTypeForPlayer(referenceFinderBuildingType, playerId).get(0); break; } } if (referencePosition != null) { PlayerStatistic playerStatistic = playerStatistics[playerId]; playerStatistic.referencePosition = referencePosition; playerStatistic.partitionIdToBuildOn = partitionsGrid.getPartitionIdAt(referencePosition.x, referencePosition.y); playerStatistic.blockedPartitionId = landscapeGrid.getBlockedPartitionAt(referencePosition.x, referencePosition.y); playerStatistic.materialProduction = partitionsGrid.getMaterialProductionAt(referencePosition.x, referencePosition.y); playerStatistic.materials = partitionsGrid.getPartitionDataForManagerAt(referencePosition.x, referencePosition.y); } } } public Building getBuildingAt(ShortPoint2D point) { return (Building) objectsGrid.getMapObjectAt(point.x, point.y, EMapObjectType.BUILDING); } public ShortPoint2D getNearestResourcePointForPlayer(ShortPoint2D point, EResourceType resourceType, byte playerId, int searchDistance, AiPositionFilter filter) { return getNearestPointInDefaultPartitionOutOfSortedMap(point, sortedResourceTypes[resourceType.ordinal], playerId, searchDistance, filter); } public ShortPoint2D getNearestFishPointForPlayer(ShortPoint2D point, final byte playerId, int currentNearestPointDistance) { return sortedResourceTypes[EResourceType.FISH.ordinal].getNearestPoint(point, currentNearestPointDistance, new AiPositionFilter() { @Override public boolean contains(int x, int y) { return isPlayerThere(x + 3, y) || isPlayerThere(x - 3, y) || isPlayerThere(x, y + 3) || isPlayerThere(x, y - 3); } private boolean isPlayerThere(int x, int y) { return mainGrid.isInBounds(x, y) && partitionsGrid.getPartitionAt(x, y).getPlayerId() == playerId; } }); } public ShortPoint2D getNearestResourcePointInDefaultPartitionFor(ShortPoint2D point, EResourceType resourceType, int currentNearestPointDistance, AiPositionFilter filter) { return getNearestResourcePointForPlayer(point, resourceType, (byte) -1, currentNearestPointDistance, filter); } public ShortPoint2D getNearestCuttableObjectPointInDefaultPartitionFor(ShortPoint2D point, EMapObjectType cuttableObject, int searchDistance, AiPositionFilter filter) { return getNearestCuttableObjectPointForPlayer(point, cuttableObject, searchDistance, (byte) -1, filter); } public ShortPoint2D getNearestCuttableObjectPointForPlayer(ShortPoint2D point, EMapObjectType cuttableObject, int searchDistance, byte playerId, AiPositionFilter filter) { AiPositions sortedResourcePoints = sortedCuttableObjectsInDefaultPartition.get(cuttableObject); if (sortedResourcePoints == null) { return null; } return getNearestPointInDefaultPartitionOutOfSortedMap(point, sortedResourcePoints, playerId, searchDistance, filter); } private ShortPoint2D getNearestPointInDefaultPartitionOutOfSortedMap(ShortPoint2D point, AiPositions sortedPoints, final byte playerId, int searchDistance, final AiPositionFilter filter) { return sortedPoints.getNearestPoint(point, searchDistance, new AiPositions.CombinedAiPositionFilter(new AiPositionFilter() { @Override public boolean contains(int x, int y) { return partitionsGrid.getPartitionAt(x, y).getPlayerId() == playerId; } }, filter)); } public boolean hasPlayersBlockedPartition(byte playerId, int x, int y) { return landscapeGrid.getBlockedPartitionAt(x, y) == playerStatistics[playerId].blockedPartitionId; } public List<ShortPoint2D> getMovablePositionsByTypeForPlayer(EMovableType movableType, byte playerId) { if (!playerStatistics[playerId].movablePositions.containsKey(movableType)) { return Collections.emptyList(); } return playerStatistics[playerId].movablePositions.get(movableType); } public int getTotalNumberOfBuildingTypeForPlayer(EBuildingType type, byte playerId) { return playerStatistics[playerId].totalBuildingsNumbers[type.ordinal]; } public int getTotalWineCountForPlayer(byte playerId) { return playerStatistics[playerId].wineCount; } public int getNumberOfBuildingTypeForPlayer(EBuildingType type, byte playerId) { return playerStatistics[playerId].buildingsNumbers[type.ordinal]; } public int getNumberOfNotFinishedBuildingsForPlayer(byte playerId) { return playerStatistics[playerId].numberOfNotFinishedBuildings; } public int getNumberOfTotalBuildingsForPlayer(byte playerId) { return playerStatistics[playerId].numberOfTotalBuildings; } public List<ShortPoint2D> getBuildingPositionsOfTypeForPlayer(EBuildingType type, byte playerId) { if (!playerStatistics[playerId].buildingPositions.containsKey(type)) { return Collections.emptyList(); } return playerStatistics[playerId].buildingPositions.get(type); } public List<ShortPoint2D> getBuildingPositionsOfTypesForPlayer(EnumSet<EBuildingType> buildingTypes, byte playerId) { List<ShortPoint2D> buildingPositions = new Vector<ShortPoint2D>(); for (EBuildingType buildingType : buildingTypes) { buildingPositions.addAll(getBuildingPositionsOfTypeForPlayer(buildingType, playerId)); } return buildingPositions; } public AiPositions getStonesForPlayer(byte playerId) { return playerStatistics[playerId].stones; } public AiPositions getTreesForPlayer(byte playerId) { return playerStatistics[playerId].trees; } public AiPositions getLandForPlayer(byte playerId) { return playerStatistics[playerId].landToBuildOn; } public boolean blocksWorkingAreaOfOtherBuilding(ShortPoint2D point, byte playerId, EBuildingType buildingType) { for (ShortPoint2D workAreaCenter : playerStatistics[playerId].wineGrowerWorkAreas) { for (RelativePoint blockedPoint : buildingType.getBlockedTiles()) { if (workAreaCenter.getOnGridDistTo(blockedPoint.calculatePoint(point)) <= EBuildingType.WINEGROWER.getWorkRadius()) { return true; } } } for (ShortPoint2D workAreaCenter : playerStatistics[playerId].farmWorkAreas) { for (RelativePoint blockedPoint : buildingType.getBlockedTiles()) { if (workAreaCenter.getOnGridDistTo(blockedPoint.calculatePoint(point)) <= EBuildingType.FARM.getWorkRadius()) { return true; } } } return false; } public boolean southIsFreeForPlayer(ShortPoint2D point, byte playerId) { return pointIsFreeForPlayer(point.x, (short) (point.y + 12), playerId) && pointIsFreeForPlayer((short) (point.x + 5), (short) (point.y + 12), playerId) && pointIsFreeForPlayer((short) (point.x + 10), (short) (point.y + 12), playerId) && pointIsFreeForPlayer(point.x, (short) (point.y + 6), playerId) && pointIsFreeForPlayer( (short) (point.x + 5), (short) (point.y + 6), playerId) && pointIsFreeForPlayer((short) (point.x + 10), (short) (point.y + 6), playerId); } private boolean pointIsFreeForPlayer(short x, short y, byte playerId) { return mainGrid.isInBounds(x, y) && partitionsGrid.getPlayerIdAt(x, y) == playerId && !objectsGrid.isBuildingAt(x, y) && !flagsGrid.isProtected(x, y) && landscapeGrid.isHexAreaOfType(x, y, 0, 2, ELandscapeType.GRASS, ELandscapeType.EARTH); } public boolean wasFishNearByAtGameStart(ShortPoint2D position) { return aiMapInformation.wasFishNearByAtGameStart.get(position.x * partitionsGrid.getWidth() + position.y); } public IMovable getNearestSwordsmanOf(ShortPoint2D targetPosition, byte playerId) { List<ShortPoint2D> soldierPositions = getMovablePositionsByTypeForPlayer(SWORDSMAN_L3, playerId); if (soldierPositions.size() == 0) { soldierPositions = getMovablePositionsByTypeForPlayer(SWORDSMAN_L2, playerId); } if (soldierPositions.size() == 0) { soldierPositions = getMovablePositionsByTypeForPlayer(SWORDSMAN_L1, playerId); } if (soldierPositions.size() == 0) { return null; } ShortPoint2D nearestSoldierPosition = detectNearestPointFromList(targetPosition, soldierPositions); return movableGrid.getMovableAt(nearestSoldierPosition.x, nearestSoldierPosition.y); } public static ShortPoint2D detectNearestPointFromList(ShortPoint2D referencePoint, List<ShortPoint2D> points) { if (points.isEmpty()) { return null; } return detectNearestPointsFromList(referencePoint, points, 1).get(0); } public static List<ShortPoint2D> detectNearestPointsFromList(final ShortPoint2D referencePoint, List<ShortPoint2D> points, int amountOfPointsToDetect) { if (amountOfPointsToDetect <= 0) { return Collections.emptyList(); } if (points.size() <= amountOfPointsToDetect) { return points; } Collections.sort(points, Comparators.comparingInt(o -> o.getOnGridDistTo(referencePoint))); return points.subList(0, amountOfPointsToDetect); } public int getNumberOfMaterialTypeForPlayer(EMaterialType type, byte playerId) { if (playerStatistics[playerId].materials == null) { return 0; } return playerStatistics[playerId].materials.getAmountOf(type); } public MainGrid getMainGrid() { return mainGrid; } public ShortPoint2D getNearestRiverPointInDefaultPartitionFor(ShortPoint2D referencePoint, int searchDistance, AiPositionFilter filter) { return getNearestPointInDefaultPartitionOutOfSortedMap(referencePoint, sortedRiversInDefaultPartition, (byte) -1, searchDistance, filter); } public int getNumberOfNotFinishedBuildingTypesForPlayer(EBuildingType buildingType, byte playerId) { return getTotalNumberOfBuildingTypeForPlayer(buildingType, playerId) - getNumberOfBuildingTypeForPlayer(buildingType, playerId); } public AiPositions getRiversForPlayer(byte playerId) { return playerStatistics[playerId].rivers; } public List<Byte> getEnemiesOf(byte playerId) { List<Byte> enemies = new ArrayList<Byte>(); for (Team team : partitionsGrid.getTeams()) { if (!team.isMember(playerId)) { for (Player player : team.getMembers()) { enemies.add(player.playerId); } } } return enemies; } public List<Byte> getAliveEnemiesOf(byte playerId) { List<Byte> aliveEnemies = new ArrayList<>(); for (byte enemyId : getEnemiesOf(playerId)) { if (isAlive(enemyId)) { aliveEnemies.add(enemyId); } } return aliveEnemies; } public ShortPoint2D calculateAveragePointFromList(List<ShortPoint2D> points) { int averageX = 0; int averageY = 0; for (ShortPoint2D point : points) { averageX += point.x; averageY += point.y; } return new ShortPoint2D(averageX / points.size(), averageY / points.size()); } public AiPositions getEnemiesInTownOf(byte playerId) { return playerStatistics[playerId].enemyTroopsInTown; } public IMaterialProductionSettings getMaterialProduction(byte playerId) { return playerStatistics[playerId].materialProduction; } public ShortPoint2D getPositionOfPartition(byte playerId) { return playerStatistics[playerId].referencePosition; } public AiPositions getBorderOf(byte playerId) { return playerStatistics[playerId].border; } public AiPositions getOtherPartitionBorderOf(byte playerId) { return playerStatistics[playerId].otherPartitionBorder; } public boolean isAlive(byte playerId) { return playerStatistics[playerId].isAlive; } public AiMapInformation getAiMapInformation() { return aiMapInformation; } public long resourceCountInDefaultPartition(EResourceType resourceType) { return resourceCountInDefaultPartition[resourceType.ordinal]; } public long resourceCountOfPlayer(EResourceType resourceType, byte playerId) { return playerStatistics[playerId].resourceCount[resourceType.ordinal]; } public List<ShortPoint2D> threatenedBorderOf(byte playerId) { if (playerStatistics[playerId].threatenedBorder == null) { AiPositions borderOfOtherPlayers = new AiPositions(); for (byte otherPlayerId = 0; otherPlayerId < playerStatistics.length; otherPlayerId++) { if (otherPlayerId == playerId || !isAlive(otherPlayerId)) { continue; } borderOfOtherPlayers.addAllNoCollision(getBorderOf(otherPlayerId)); } playerStatistics[playerId].threatenedBorder = new ArrayList<>(); AiPositions myBorder = getBorderOf(playerId); for (int i = 0; i < myBorder.size(); i += 10) { ShortPoint2D myBorderPosition = myBorder.get(i); if (mainGrid.getPartitionsGrid().getTowerCountAt(myBorderPosition.x, myBorderPosition.y) == 0 && borderOfOtherPlayers.getNearestPoint(myBorderPosition, CommonConstants.TOWER_RADIUS) != null) { playerStatistics[playerId].threatenedBorder.add(myBorderPosition); } } } return playerStatistics[playerId].threatenedBorder; } public AiPositions getStonesNearBy(byte playerId) { return playerStatistics[playerId].stonesNearBy; } private static class PlayerStatistic { ShortPoint2D referencePosition; boolean isAlive; final int[] totalBuildingsNumbers = new int[EBuildingType.NUMBER_OF_BUILDINGS]; final int[] buildingsNumbers = new int[EBuildingType.NUMBER_OF_BUILDINGS]; final Map<EBuildingType, List<ShortPoint2D>> buildingPositions = new HashMap<EBuildingType, List<ShortPoint2D>>(); final List<ShortPoint2D> farmWorkAreas = new Vector<ShortPoint2D>(); final List<ShortPoint2D> wineGrowerWorkAreas = new Vector<ShortPoint2D>(); short partitionIdToBuildOn; public short blockedPartitionId; IPartitionData materials; final AiPositions landToBuildOn = new AiPositions(); final AiPositions border = new AiPositions(); final AiPositions otherPartitionBorder = new AiPositions(); final Map<EMovableType, List<ShortPoint2D>> movablePositions = new HashMap<EMovableType, List<ShortPoint2D>>(); final AiPositions stones = new AiPositions(); final AiPositions stonesNearBy = new AiPositions(); final AiPositions trees = new AiPositions(); final AiPositions rivers = new AiPositions(); final AiPositions enemyTroopsInTown = new AiPositions(); List<ShortPoint2D> threatenedBorder; final long[] resourceCount = new long[EResourceType.VALUES.length]; int numberOfNotFinishedBuildings; int numberOfTotalBuildings; int numberOfNotOccupiedMilitaryBuildings; int wineCount; IMaterialProductionSettings materialProduction; PlayerStatistic() { clearIntegers(); } public void clearAll() { isAlive = false; materials = null; buildingPositions.clear(); enemyTroopsInTown.clear(); stones.clear(); stonesNearBy.clear(); trees.clear(); rivers.clear(); landToBuildOn.clear(); border.clear(); otherPartitionBorder.clear(); movablePositions.clear(); farmWorkAreas.clear(); wineGrowerWorkAreas.clear(); threatenedBorder = null; clearIntegers(); } private void clearIntegers() { Arrays.fill(totalBuildingsNumbers, 0); Arrays.fill(buildingsNumbers, 0); Arrays.fill(resourceCount, 0); numberOfNotFinishedBuildings = 0; numberOfTotalBuildings = 0; numberOfNotOccupiedMilitaryBuildings = 0; wineCount = 0; partitionIdToBuildOn = Short.MIN_VALUE; blockedPartitionId = Short.MIN_VALUE; } } }