package tools.image; import java.awt.Point; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.Rectangle2D; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.LineNumberReader; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import games.strategy.debug.ClientLogger; import games.strategy.engine.ClientFileSystemHelper; import games.strategy.triplea.image.UnitImageFactory; import games.strategy.triplea.ui.mapdata.MapData; import games.strategy.util.PointFileReaderWriter; import tools.map.making.JTextAreaOptionPane; public class AutoPlacementFinder { private static int PLACEWIDTH = UnitImageFactory.DEFAULT_UNIT_ICON_SIZE; private static int PLACEHEIGHT = UnitImageFactory.DEFAULT_UNIT_ICON_SIZE; private static MapData s_mapData; private static boolean placeDimensionsSet = false; private static double unit_zoom_percent = 1; private static int unit_width = UnitImageFactory.DEFAULT_UNIT_ICON_SIZE; private static int unit_height = UnitImageFactory.DEFAULT_UNIT_ICON_SIZE; private static File s_mapFolderLocation = null; private static final String TRIPLEA_MAP_FOLDER = "triplea.map.folder"; private static final String TRIPLEA_UNIT_ZOOM = "triplea.unit.zoom"; private static final String TRIPLEA_UNIT_WIDTH = "triplea.unit.width"; private static final String TRIPLEA_UNIT_HEIGHT = "triplea.unit.height"; private static final JTextAreaOptionPane textOptionPane = new JTextAreaOptionPane(null, "AutoPlacementFinder Log\r\n\r\n", "", "AutoPlacementFinder Log", null, 500, 300, true, 1, null); public static String[] getProperties() { return new String[] {TRIPLEA_MAP_FOLDER, TRIPLEA_UNIT_ZOOM, TRIPLEA_UNIT_WIDTH, TRIPLEA_UNIT_HEIGHT}; } public static void main(final String[] args) { handleCommandLineArgs(args); JOptionPane.showMessageDialog(null, new JLabel("<html>" + "This is the AutoPlacementFinder, it will create a place.txt file for you. " + "<br>In order to run this, you must already have created a centers.txt file and a polygons.txt file, " + "<br>and you must have already created the map directory structure in its final place." + "<br>Example: the map folder should have a name, with the 2 text files already in that folder, and " + "<br>the folder should be located in your users\\yourname\\triplea\\maps\\ directory." + "<br><br>The program will ask for the folder name (just the name, not the full path)." + "<br>Then it will ask for unit scale (unit zoom) level [normally between 0.5 and 1.0]" + "<br>Then it will ask for the unit image size when not zoomed [normally 48x48]." + "<br><br>If you want to have less, or more, room around the edges of your units, you can change the unit " + "size." + "<br><br>When done, the program will attempt to make placements for all territories on your map." + "<br>However, it doesn't do a good job with thin or small territories, or islands, so it is a very good " + "idea" + "<br>to use the PlacementPicker to check all placements and redo some of them by hand." + "</html>")); calculate(); } /** * calculate() * Will calculate the placements on the map automatically. */ static void calculate() { // create hash map of placements final Map<String, Collection<Point>> m_placements = new HashMap<>(); // ask user where the map is final String mapDir = s_mapFolderLocation == null ? getMapDirectory() : s_mapFolderLocation.getName(); if (mapDir == null) { System.out.println("You need to specify a map name for this to work"); System.out.println("Shutting down"); System.exit(0); } final File file = getMapPropertiesFile(mapDir); if (file.exists() && s_mapFolderLocation == null) { s_mapFolderLocation = file.getParentFile(); } if (!placeDimensionsSet) { try { if (file.exists()) { double scale = unit_zoom_percent; int width = unit_width; int height = unit_height; boolean found = false; final String scaleProperty = MapData.PROPERTY_UNITS_SCALE + "="; final String widthProperty = MapData.PROPERTY_UNITS_WIDTH + "="; final String heightProperty = MapData.PROPERTY_UNITS_HEIGHT + "="; final FileReader reader = new FileReader(file); final LineNumberReader reader2 = new LineNumberReader(reader); int i = 0; while (true) { reader2.setLineNumber(i); final String line = reader2.readLine(); if (line == null) { break; } if (line.contains(scaleProperty)) { try { scale = Double.parseDouble(line.substring(line.indexOf(scaleProperty) + scaleProperty.length()).trim()); found = true; } catch (final NumberFormatException ex) { // ignore malformed input } } if (line.contains(widthProperty)) { try { width = Integer.parseInt(line.substring(line.indexOf(widthProperty) + widthProperty.length()).trim()); found = true; } catch (final NumberFormatException ex) { // ignore malformed input } } if (line.contains(heightProperty)) { try { height = Integer.parseInt(line.substring(line.indexOf(heightProperty) + heightProperty.length()).trim()); found = true; } catch (final NumberFormatException ex) { // ignore malformed input } } } reader2.close(); i++; if (found) { final int result = JOptionPane.showConfirmDialog(new JPanel(), "A map.properties file was found in the map's folder, " + "\r\n do you want to use the file to supply the info for the placement box size? " + "\r\n Zoom = " + scale + ", Width = " + width + ", Height = " + height + ", Result = (" + ((int) (scale * width)) + "x" + ((int) (scale * height)) + ")", "File Suggestion", 1); if (result == 0) { unit_zoom_percent = scale; PLACEWIDTH = (int) (unit_zoom_percent * width); PLACEHEIGHT = (int) (unit_zoom_percent * height); placeDimensionsSet = true; } } } } catch (final Exception ex) { ClientLogger.logQuietly(ex); } } if (!placeDimensionsSet || JOptionPane.showConfirmDialog(new JPanel(), "Placement Box Size already set (" + PLACEWIDTH + "x" + PLACEHEIGHT + "), " + "do you wish to continue with this?\r\n" + "Select Yes to continue, Select No to override and change the size.", "Placement Box Size", JOptionPane.YES_NO_OPTION) == 1) { try { final String result = getUnitsScale(); try { unit_zoom_percent = Double.parseDouble(result.toLowerCase()); } catch (final NumberFormatException ex) { // ignore malformed input } final String width = JOptionPane.showInputDialog(null, "Enter the unit's image width in pixels (unscaled / without zoom).\r\n(e.g. 48)"); if (width != null) { try { PLACEWIDTH = (int) (unit_zoom_percent * Integer.parseInt(width)); } catch (final NumberFormatException ex) { // ignore malformed input } } final String height = JOptionPane.showInputDialog(null, "Enter the unit's image height in pixels (unscaled / without zoom).\r\n(e.g. 48)"); if (height != null) { try { PLACEHEIGHT = (int) (unit_zoom_percent * Integer.parseInt(height)); } catch (final NumberFormatException ex) { // ignore malformed input } } placeDimensionsSet = true; } catch (final Exception ex) { ClientLogger.logQuietly(ex); } } try { // makes TripleA read all the text data files for the map. s_mapData = new MapData(mapDir); } catch (final Exception ex) { JOptionPane.showMessageDialog(null, new JLabel( "Could not find map. Make sure it is in finalized location and contains centers.txt and polygons.txt")); System.out.println("Caught Exception."); System.out.println("Could be due to some missing text files."); System.out.println("Or due to the map folder not being in the right location."); ex.printStackTrace(); System.exit(0); } textOptionPane.show(); textOptionPane.appendNewLine("Place Dimensions in pixels, being used: " + PLACEWIDTH + "x" + PLACEHEIGHT + "\r\n"); textOptionPane.appendNewLine("Calculating, this may take a while...\r\n"); final Iterator<String> terrIter = s_mapData.getTerritories().iterator(); while (terrIter.hasNext()) { final String name = terrIter.next(); List<Point> points; if (s_mapData.hasContainedTerritory(name)) { final Set<Polygon> containedPolygons = new HashSet<>(); for (final String containedName : s_mapData.getContainedTerritory(name)) { containedPolygons.addAll(s_mapData.getPolygons(containedName)); } points = getPlacementsStartingAtTopLeft(s_mapData.getPolygons(name), s_mapData.getBoundingRect(name), s_mapData.getCenter(name), containedPolygons); m_placements.put(name, points); } else { points = getPlacementsStartingAtMiddle(s_mapData.getPolygons(name), s_mapData.getBoundingRect(name), s_mapData.getCenter(name)); m_placements.put(name, points); } textOptionPane.appendNewLine(name + ": " + points.size()); } // while textOptionPane.appendNewLine("\r\nAll Finished!"); textOptionPane.countDown(); try { final String fileName = new FileSave("Where To Save place.txt ?", "place.txt", s_mapFolderLocation).getPathString(); if (fileName == null) { textOptionPane.appendNewLine("You chose not to save, Shutting down"); textOptionPane.dispose(); System.exit(0); } PointFileReaderWriter.writeOneToMany(new FileOutputStream(fileName), m_placements); textOptionPane.appendNewLine("Data written to :" + new File(fileName).getCanonicalPath()); } catch (final Exception ex) { ex.printStackTrace(); textOptionPane.dispose(); System.exit(0); } textOptionPane.dispose(); // shut down program when done. System.exit(0); } /** * we need the exact map name as indicated in the XML game file * ie. "revised" "classic" "pact_of_steel" * of course, without the quotes. */ private static String getMapDirectory() { final String mapDir = JOptionPane.showInputDialog(null, "Enter the map name (ie. folder name)"); if (mapDir != null) { return mapDir; } else { return null; } } private static File getMapPropertiesFile(final String mapDir) { final File file = getMapPropertiesFileForCurrentFolderStructure(mapDir); if (file.exists()) { return file; } return getMapPropertiesFileForLegacyFolderStructure(mapDir); } private static File getMapPropertiesFileForCurrentFolderStructure(final String mapDir) { return new File(ClientFileSystemHelper.getUserMapsFolder(), Paths.get(mapDir, "map", "map.properties").toString()); } private static File getMapPropertiesFileForLegacyFolderStructure(final String mapDir) { return new File(ClientFileSystemHelper.getUserMapsFolder(), Paths.get(mapDir, "map.properties").toString()); } private static String getUnitsScale() { final String unitsScale = JOptionPane.showInputDialog(null, "Enter the unit's scale (zoom).\r\n(e.g. 1.25, 1, 0.875, 0.8333, 0.75, 0.6666, 0.5625, 0.5)"); if (unitsScale != null) { return unitsScale; } else { return "1"; } } /** * java.tools.List getPlacementsStartingAtMiddle(java.tools.Collection, java.awt.Rectangle, java.awt.Point) * * @param java * .tools.Collection * @param java * .awt.Rectangle * @param java * .awt.Point * @return java.tools.List */ static List<Point> getPlacementsStartingAtMiddle(final Collection<Polygon> countryPolygons, final Rectangle bounding, final Point center) { final List<Rectangle2D> placementRects = new ArrayList<>(); final List<Point> placementPoints = new ArrayList<>(); final Rectangle2D place = new Rectangle2D.Double(center.x, center.y, PLACEHEIGHT, PLACEWIDTH); int x = center.x - (PLACEHEIGHT / 2); int y = center.y - (PLACEWIDTH / 2); int step = 1; for (int i = 0; i < 2 * Math.max(bounding.width, bounding.height); i++) { for (int j = 0; j < Math.abs(step); j++) { if (step > 0) { x++; } else { x--; } isPlacement(countryPolygons, Collections.emptySet(), placementRects, placementPoints, place, x, y); } for (int j = 0; j < Math.abs(step); j++) { if (step > 0) { y++; } else { y--; } isPlacement(countryPolygons, Collections.emptySet(), placementRects, placementPoints, place, x, y); } step = -step; if (step > 0) { step++; } else { step--; } // For Debugging // textOptionPane.appendNewLine("x:" + x + " y:" + y); if (placementPoints.size() > 50) { break; } } if (placementPoints.isEmpty()) { final int defaultx = center.x - (PLACEHEIGHT / 2); final int defaulty = center.y - (PLACEWIDTH / 2); placementPoints.add(new Point(defaultx, defaulty)); } return placementPoints; } /** * java.tools.List getPlacementsStartingAtTopLeft(java.tools.Collection, java.awt.Rectangle, java.awt.Point, * java.tools.Collection) * * @param java * .tools.Collection * @param java * .awt.Rectangle * @param java * .awt.Point * @param java * .tools.Collection * @return java.tools.List */ static List<Point> getPlacementsStartingAtTopLeft(final Collection<Polygon> countryPolygons, final Rectangle bounding, final Point center, final Collection<Polygon> containedCountryPolygons) { final List<Rectangle2D> placementRects = new ArrayList<>(); final List<Point> placementPoints = new ArrayList<>(); final Rectangle2D place = new Rectangle2D.Double(center.x, center.y, PLACEHEIGHT, PLACEWIDTH); for (int x = bounding.x + 1; x < bounding.width + bounding.x; x++) { for (int y = bounding.y + 1; y < bounding.height + bounding.y; y++) { isPlacement(countryPolygons, containedCountryPolygons, placementRects, placementPoints, place, x, y); } if (placementPoints.size() > 50) { break; } } if (placementPoints.isEmpty()) { final int defaultx = center.x - (PLACEHEIGHT / 2); final int defaulty = center.y - (PLACEWIDTH / 2); placementPoints.add(new Point(defaultx, defaulty)); } return placementPoints; } /** * isPlacement(java.tools.Collection, java.tools.Collection, java.tools.List, java.tools.List, * java.awt.geom.Rectangle2D, * java.lang.int, * java.lang.int) * * @param java * .tools.Collection countryPolygons * @param java * .tools.Collection containedCountryPolygons polygons of countries contained with ourselves * @param java * .tools.List placementRects * @param java * .tools.List placementPoints * @param java * .awt.geom.Rectangle2D place * @param java * .lang.int x * @param java * .lang.int y */ private static void isPlacement(final Collection<Polygon> countryPolygons, final Collection<Polygon> containedCountryPolygons, final List<Rectangle2D> placementRects, final List<Point> placementPoints, final Rectangle2D place, final int x, final int y) { place.setFrame(x, y, PLACEWIDTH, PLACEHEIGHT); if (containedIn(place, countryPolygons) && !intersectsOneOf(place, placementRects) // make sure it is not in or intersects the contained country && (!containedIn(place, containedCountryPolygons) && !intersectsOneOf(place, containedCountryPolygons))) { placementPoints.add(new Point((int) place.getX(), (int) place.getY())); final Rectangle2D newRect = new Rectangle2D.Double(); newRect.setFrame(place); placementRects.add(newRect); } // if } /** * java.lang.boolean containedIn(java.awt.geom.Rectangle2D, java.tools.Collection) * Function to test if the given 2D rectangle * is contained in any of the given shapes * in the collection. * * @param java * .awt.geom.Rectangle2D r * @param java * .tools.Collection shapes */ private static boolean containedIn(final Rectangle2D r, final Collection<Polygon> shapes) { for (final Shape item : shapes) { if (item.contains(r)) { return true; } } return false; } /** * java.lang.boolean intersectsOneOf(java.awt.geom.Rectangle2D, java.tools.Collection) * Function to test if the given 2D rectangle * intersects any of the shapes given in the * collection. * * @param java * .awt.geom.Rectangle2D r * @param java * .tools.Collection shapes */ private static boolean intersectsOneOf(final Rectangle2D r, final Collection<? extends Shape> shapes) { if (shapes.isEmpty()) { return false; } for (final Shape item : shapes) { if (item.intersects(r)) { return true; } } return false; } private static String getValue(final String arg) { final int index = arg.indexOf('='); if (index == -1) { return ""; } return arg.substring(index + 1); } private static void handleCommandLineArgs(final String[] args) { final String[] properties = getProperties(); if (args.length == 1) { String value; if (args[0].startsWith(TRIPLEA_UNIT_ZOOM)) { value = getValue(args[0]); } else { value = args[0]; } try { Double.parseDouble(value); System.setProperty(TRIPLEA_UNIT_ZOOM, value); } catch (final Exception ex) { // ignore malformed input } } else if (args.length == 2) { String value0; if (args[0].startsWith(TRIPLEA_UNIT_WIDTH)) { value0 = getValue(args[0]); } else { value0 = args[0]; } try { Integer.parseInt(value0); System.setProperty(TRIPLEA_UNIT_WIDTH, value0); } catch (final Exception ex) { // ignore malformed input } String value1; if (args[0].startsWith(TRIPLEA_UNIT_HEIGHT)) { value1 = getValue(args[1]); } else { value1 = args[1]; } try { Integer.parseInt(value1); System.setProperty(TRIPLEA_UNIT_HEIGHT, value1); } catch (final Exception ex) { // ignore malformed input } } boolean usagePrinted = false; for (final String arg2 : args) { boolean found = false; String arg = arg2; final int indexOf = arg.indexOf('='); if (indexOf > 0) { arg = arg.substring(0, indexOf); for (final String propertie : properties) { if (arg.equals(propertie)) { final String value = getValue(arg2); System.getProperties().setProperty(propertie, value); System.out.println(propertie + ":" + value); found = true; break; } } } if (!found) { System.out.println("Unrecogized:" + arg2); if (!usagePrinted) { usagePrinted = true; System.out.println("Arguments\r\n" + " " + TRIPLEA_MAP_FOLDER + "=<FILE_PATH>\r\n" + " " + TRIPLEA_UNIT_ZOOM + "=<UNIT_ZOOM_LEVEL>\r\n" + " " + TRIPLEA_UNIT_WIDTH + "=<UNIT_WIDTH>\r\n" + " " + TRIPLEA_UNIT_HEIGHT + "=<UNIT_HEIGHT>\r\n"); } } } final String folderString = System.getProperty(TRIPLEA_MAP_FOLDER); if (folderString != null && folderString.length() > 0) { final File mapFolder = new File(folderString); if (mapFolder.exists()) { s_mapFolderLocation = mapFolder; } else { System.out.println("Could not find directory: " + folderString); } } final String zoomString = System.getProperty(TRIPLEA_UNIT_ZOOM); if (zoomString != null && zoomString.length() > 0) { try { unit_zoom_percent = Double.parseDouble(zoomString); System.out.println("Unit Zoom Percent to use: " + unit_zoom_percent); placeDimensionsSet = true; } catch (final Exception ex) { System.err.println("Not a decimal percentage: " + zoomString); } } final String widthString = System.getProperty(TRIPLEA_UNIT_WIDTH); if (widthString != null && widthString.length() > 0) { try { unit_width = Integer.parseInt(widthString); System.out.println("Unit Width to use: " + unit_width); placeDimensionsSet = true; } catch (final Exception ex) { System.err.println("Not an integer: " + widthString); } } final String heightString = System.getProperty(TRIPLEA_UNIT_HEIGHT); if (heightString != null && heightString.length() > 0) { try { unit_height = Integer.parseInt(heightString); System.out.println("Unit Height to use: " + unit_height); placeDimensionsSet = true; } catch (final Exception ex) { System.err.println("Not an integer: " + heightString); } } if (placeDimensionsSet) { PLACEWIDTH = (int) (unit_zoom_percent * unit_width); PLACEHEIGHT = (int) (unit_zoom_percent * unit_height); System.out.println("Place Dimensions to use: " + PLACEWIDTH + "x" + PLACEHEIGHT); } } }