package onlinefrontlines.game; import java.awt.Point; import java.util.Collections; import java.util.ArrayList; import java.util.PriorityQueue; import java.util.Random; import onlinefrontlines.profiler.Profiler; import onlinefrontlines.profiler.Sampler; import onlinefrontlines.utils.HexagonGridImpl; /** * Helper class to determine which units can go where initially * * @author jorrit * * Copyright (C) 2009-2013 Jorrit Rouwe * * This file is part of Online Frontlines. * * Online Frontlines is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Online Frontlines is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Online Frontlines. If not, see <http://www.gnu.org/licenses/>. */ public class DeploymentHelper { /** * Random generator */ private Random random; /** * Cached country config */ private CountryConfig countryConfig; /** * Cached map config */ private MapConfig mapConfig; /** * Generated unit list */ ArrayList<UnitState> unitList; /** * Units on grid */ private UnitState[] unitGrid; /** * Next unit id */ private int unitId; /** * Warnings generated during deployment */ private ArrayList<String> warnings; /** * Constructor */ public DeploymentHelper(Random random) { this.random = random; } /** * Get deployment for country */ public ArrayList<UnitState> getDeployment(CountryConfig countryConfig) throws DeploymentFailedException { Sampler sampler = Profiler.getInstance().startSampler(Profiler.CATEGORY_GENERAL, "DeploymentHelper.getDeployment"); try { // Cache country and map this.countryConfig = countryConfig; mapConfig = countryConfig.getMapConfig(); // Create list/grid unitList = new ArrayList<UnitState>(); unitGrid = new UnitState[mapConfig.sizeX * mapConfig.sizeY]; // Create warnings array warnings = new ArrayList<String>(); // Get deployment for each faction getDeploymentForFaction(Faction.f1); getDeploymentForFaction(Faction.f2); // Randomize units Collections.shuffle(unitList, random); // Assign id's unitId = 1; for (UnitState u : unitList) assignId(u); return unitList; } finally { sampler.stop(); } } /** * Get warnings while generating */ public ArrayList<String> getWarnings() { return warnings; } /** * Recursively assign Id's */ private void assignId(UnitState u) { u.id = unitId++; for (UnitState c : u.containedUnits) assignId(c); } /** * Get unit on grid */ private UnitState getUnit(int x, int y) { return unitGrid[x + y * mapConfig.sizeX]; } /** * Add unit to grid */ private void addUnit(UnitState unit) { UnitState container = getUnit(unit.locationX, unit.locationY); if (container != null) { unit.container = container; container.containedUnits.add(unit); } else { unitGrid[unit.locationX + unit.locationY * mapConfig.sizeX] = unit; unitList.add(unit); } } /** * Class that keeps track of a deployment location */ private class Location implements Comparable<Location> { public int x; public int y; public int cost; public int distToCenter; public Location(int x, int y, int cost, int distToCenter) { this.x = x; this.y = y; this.cost = cost; this.distToCenter = distToCenter; } @Override public int compareTo(Location other) { // First base on cost int diff = cost - other.cost; if (diff != 0) return diff; // Then base on distance to center return distToCenter - other.distToCenter; } } /** * Helper class to contain deployment info */ private static class Deployment implements Comparable<Deployment> { /** * Config of unit to place */ public UnitConfig unitConfig; /** * Amount of units to place */ public int amount; /** * Possible locations of unit */ public ArrayList<Location> locations = new ArrayList<Location>(); /** * Comparator that sorts on bases first then on least amount of placement locations */ @Override public int compareTo(Deployment other) { // Bases go first if (unitConfig.isBase != other.unitConfig.isBase) return unitConfig.isBase? -1 : 1; // Units that have less spots to place them on go first int l = locations.size() - amount; int r = other.locations.size() - other.amount; return l - r; } } /** * Class that keeps track of cost */ @SuppressWarnings("serial") private static class Cost extends Point implements Comparable<Cost> { public int cost; public Cost(int x, int y, int cost) { super(x, y); this.cost = cost; } @Override public int compareTo(Cost other) { return cost - other.cost; } } /** * Calculate grid of movement costs to enemy terrain for unit */ private HexagonGridImpl<Integer> getMovementCostToEnemyTerrain(UnitConfig config, Faction enemyFaction) { PriorityQueue<Cost> openList = new PriorityQueue<Cost>(); // Get list of enemy terrain and init cost to 0 for (int y = 1; y < mapConfig.sizeY - 1; ++y) for (int x = 1; x < mapConfig.sizeX - 1; ++x) if (mapConfig.getTerrainOwnerAt(x, y) == enemyFaction) openList.add(new Cost(x, y, 0)); // Initialize grid HexagonGridImpl<Integer> grid = new HexagonGridImpl<Integer>(mapConfig.sizeX, mapConfig.sizeY, Integer.MAX_VALUE); Cost p; while ((p = openList.poll()) != null) { // Check to see if we found a better route if (grid.get(p) <= p.cost) continue; // Store best so far grid.set(p, p.cost); // Add neighbours to open list for (Point n : mapConfig.getNeighbours(p)) { TerrainConfig terrain = mapConfig.getTerrainAt(n.x, n.y); UnitMovementCostProperties cp = config.movementCostProperties.get(terrain.id); openList.add(new Cost(n.x, n.y, p.cost + (cp != null? cp.movementCost : 100 * config.movementPoints))); } } return grid; } /** * Get deployment for one faction */ private void getDeploymentForFaction(Faction faction) throws DeploymentFailedException { DeploymentConfig deploymentConfig = countryConfig.getDeploymentConfig(faction == Faction.f1? 0 : 1); // Determine center of map int centerX = mapConfig.sizeX / 2; int centerY = mapConfig.sizeY / 2; // Figure out where all units could potentially go ArrayList<Deployment> unitsToBeDeployed = new ArrayList<Deployment>(); for (DeploymentAmount dpa : deploymentConfig.deploymentAmounts) { Deployment d = new Deployment(); d.unitConfig = UnitConfig.allUnitsMap.get(dpa.unitId); d.amount = dpa.amount; // Get movement cost of all cells HexagonGridImpl<Integer> movementCostGrid = getMovementCostToEnemyTerrain(d.unitConfig, Faction.opposite(faction)); // Add possible locations for (int y = 1; y < mapConfig.sizeY - 1; ++y) for (int x = 1; x < mapConfig.sizeX - 1; ++x) if (GameState.canUnitBeSetupOnHelper(mapConfig, d.unitConfig, faction, x, y)) d.locations.add(new Location(x, y, movementCostGrid.get(x, y), MapConfig.getDistance(x, y, centerX, centerY))); // Sort on closeness to the action Collections.sort(d.locations); // Bases want to be as far away as possible if (d.unitConfig.isBase) Collections.reverse(d.locations); unitsToBeDeployed.add(d); } Collections.sort(unitsToBeDeployed); // Loop all units that need to be placed ArrayList<UnitConfig> unableToPlace = new ArrayList<UnitConfig>(); for (Deployment d : unitsToBeDeployed) for (int i = 0; i < d.amount; ++i) { // Find best location to place Location bestPoint = null; int bestDistance = 0; for (Location p : d.locations) { // Check if other unit in the way if (getUnit(p.x, p.y) != null) continue; // If not a base then location was found if (!d.unitConfig.isBase) { bestPoint = p; break; } // Get closest distance to other bases int distanceToBase = Integer.MAX_VALUE; for (UnitState u : unitList) if (u.faction == faction && u.unitConfig.isBase) { int dist = u.getDistanceTo(p.x, p.y); if (dist < distanceToBase) distanceToBase = dist; } // For bases find the location that is farthest away from any other base // to avoid placement conflicts if (bestDistance < distanceToBase) { bestDistance = distanceToBase; bestPoint = p; } } if (bestPoint != null) { // Check base distance if (d.unitConfig.isBase && bestDistance <= 2) { warnings.add("Base '" + d.unitConfig.name + "' (id=" + d.unitConfig.id + ") placed too close to other base " + "on map '" + mapConfig.name + "' (id=" + mapConfig.id + ") " + "using country '" + countryConfig.name + "' (id=" + countryConfig.id + ") " + "for faction " + faction + " created by " + countryConfig.creatorUserId); } // Add the unit addUnit(new UnitState(-1, bestPoint.x, bestPoint.y, faction, d.unitConfig)); } else { // Cannot place this unit unableToPlace.add(d.unitConfig); } } // Loop through all remaining units and check if they can be put in another unit for (UnitConfig u : unableToPlace) { // Loop through all units to see if it can contain the unit boolean done = false; for (UnitState c : unitList) if (c.faction == faction && c.canHold(u)) { // Add the unit addUnit(new UnitState(-1, c.locationX, c.locationY, faction, u)); done = true; break; } // Check if successful if (!done) throw new DeploymentFailedException(u, faction, countryConfig); } } }