[vassalengine-svn] SF.net SVN: vassalengine: [2921] VASSAL-src/branches/mkiefte-ADC2-import/ src/VA
Brought to you by:
rodneykinney,
uckelman
From: <mk...@us...> - 2008-01-30 02:11:50
|
Revision: 2921 http://vassalengine.svn.sourceforge.net/vassalengine/?rev=2921&view=rev Author: mkiefte Date: 2008-01-29 18:11:54 -0800 (Tue, 29 Jan 2008) Log Message: ----------- Moved ADC2 import code to tools/foreign/adc2 directory Modified Paths: -------------- VASSAL-src/branches/mkiefte-ADC2-import/src/VASSAL/launch/ConsoleControls.java Added Paths: ----------- VASSAL-src/branches/mkiefte-ADC2-import/src/VASSAL/tools/foreign/ VASSAL-src/branches/mkiefte-ADC2-import/src/VASSAL/tools/foreign/adc2/ VASSAL-src/branches/mkiefte-ADC2-import/src/VASSAL/tools/foreign/adc2/ImportADC2Action.java Removed Paths: ------------- VASSAL-src/branches/mkiefte-ADC2-import/src/VASSAL/launch/ImportModuleAction.java Modified: VASSAL-src/branches/mkiefte-ADC2-import/src/VASSAL/launch/ConsoleControls.java =================================================================== --- VASSAL-src/branches/mkiefte-ADC2-import/src/VASSAL/launch/ConsoleControls.java 2008-01-30 01:42:02 UTC (rev 2920) +++ VASSAL-src/branches/mkiefte-ADC2-import/src/VASSAL/launch/ConsoleControls.java 2008-01-30 02:11:54 UTC (rev 2921) @@ -40,6 +40,7 @@ import VASSAL.i18n.Resources; import VASSAL.preferences.Prefs; import VASSAL.tools.FileChooser; +import VASSAL.tools.foreign.adc2.ImportADC2Action; /** * The starting controls from which the user may load or edit any module @@ -141,7 +142,7 @@ editModuleAction.addAction(closeConsoleWindow); editButton = new javax.swing.JButton(editModuleAction); box.add(editButton); - ImportModuleAction importModuleAction = new ImportModuleAction(controls); + ImportADC2Action importModuleAction = new ImportADC2Action(controls); importModuleAction.addAction(closeConsoleWindow); importButton = new javax.swing.JButton(importModuleAction); box.add(importButton); Deleted: VASSAL-src/branches/mkiefte-ADC2-import/src/VASSAL/launch/ImportModuleAction.java =================================================================== --- VASSAL-src/branches/mkiefte-ADC2-import/src/VASSAL/launch/ImportModuleAction.java 2008-01-30 01:42:02 UTC (rev 2920) +++ VASSAL-src/branches/mkiefte-ADC2-import/src/VASSAL/launch/ImportModuleAction.java 2008-01-30 02:11:54 UTC (rev 2921) @@ -1,4355 +0,0 @@ -/* - * Copyright (c) 2007 by Michael Kiefte - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License (LGPL) as published by the Free Software Foundation. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, copies are available - * at http://www.opensource.org. - */ - -/******************************************************************************* - * WARNING: Your map board may be blank or the program may complain that it does - * not have enough memory. If so go to Preferences->Java->Installed JREs, click - * on your JRE, hit "edit" and under "Default VM Arguments" put in something - * like -Xmx256m -Xms256m to increas the RAM allocated to your JVM. Some maps - * get very big, especially the ones generated from map elements. - ******************************************************************************/ - -/* TODO: Future improvements - * - user interaction to determine tweaks - * - bezier smoothing of hexlines in hexes with no intersections - */ - -package VASSAL.launch; - -import java.awt.AlphaComposite; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Graphics2D; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.event.ActionEvent; -import java.awt.font.TextAttribute; -import java.awt.geom.GeneralPath; -import java.awt.geom.Point2D; -import java.awt.image.BandCombineOp; -import java.awt.image.BufferedImage; -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.EOFException; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Hashtable; - -import javax.imageio.ImageIO; -import javax.swing.BorderFactory; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; - -import VASSAL.build.AbstractConfigurable; -import VASSAL.build.GameModule; -import VASSAL.build.module.GlobalOptions; -import VASSAL.build.module.Map; -import VASSAL.build.module.PieceWindow; -import VASSAL.build.module.PlayerRoster; -import VASSAL.build.module.PrototypeDefinition; -import VASSAL.build.module.PrototypesContainer; -import VASSAL.build.module.map.BoardPicker; -import VASSAL.build.module.map.DrawPile; -import VASSAL.build.module.map.SetupStack; -import VASSAL.build.module.map.Zoomer; -import VASSAL.build.module.map.boardPicker.Board; -import VASSAL.build.module.map.boardPicker.board.HexGrid; -import VASSAL.build.module.map.boardPicker.board.MapGrid; -import VASSAL.build.module.map.boardPicker.board.SquareGrid; -import VASSAL.build.module.map.boardPicker.board.ZonedGrid; -import VASSAL.build.module.map.boardPicker.board.mapgrid.HexGridNumbering; -import VASSAL.build.module.map.boardPicker.board.mapgrid.RegularGridNumbering; -import VASSAL.build.module.map.boardPicker.board.mapgrid.SquareGridNumbering; -import VASSAL.build.module.map.boardPicker.board.mapgrid.Zone; -import VASSAL.build.widget.CardSlot; -import VASSAL.build.widget.ListWidget; -import VASSAL.build.widget.PieceSlot; -import VASSAL.configure.ModuleEditWindow; -import VASSAL.counters.BasicPiece; -import VASSAL.counters.Deck; -import VASSAL.counters.Delete; -import VASSAL.counters.Footprint; -import VASSAL.counters.FreeRotator; -import VASSAL.counters.GamePiece; -import VASSAL.counters.MovementMarkable; -import VASSAL.counters.Replace; -import VASSAL.counters.ReturnToDeck; -import VASSAL.counters.UsePrototype; -import VASSAL.i18n.Resources; -import VASSAL.launch.ImportModuleAction.SymbolSet.SymbolData; -import VASSAL.tools.ArchiveWriter; -import VASSAL.tools.DataArchive; -import VASSAL.tools.FileChooser; -import VASSAL.tools.FileFilter; -import VASSAL.tools.SequenceEncoder; - -/** - * Import a foreign module to VASSAL. - * - * @author Michael Kiefte - * - */ - -public class ImportModuleAction extends LoadModuleAction { - - /** - * File cannot be interpreted. Either the file is not what VASSAL thinks it is - * or it is currupted in some way. - */ - public static class FileFormatException extends IOException { - - private static final long serialVersionUID = 0L; - - FileFormatException() { - } - - FileFormatException(String s) { - super(s); - } - } - - private static final long serialVersionUID = 1L; - - // which zoom level are we importing? - private int zoomLevel; - - /** - * Strip the extension from a filename and replace with the given extension. - */ - public static String forceExtension(String s, String ext) { - if (s.equals(".") || s.equals("..")) - return s; - return stripExtension(s) + '.' + ext; - } - - /** - * Get the extension from a file name. - */ - public static String getExtension(String s) { - if (s.equals(".") || s.equals("..")) - return ""; - final int extIdx = s.lastIndexOf('.'); - final int pathIdx = s.lastIndexOf(File.separatorChar); - if (extIdx == -1 || extIdx < pathIdx || extIdx >= s.length() - 1) - return ""; - else - return s.substring(extIdx + 1); - } - - /** - * Get the file name without the qualifying path. - */ - public static String getFileName(String s) { - if (s.equals(".") || s.equals("..")) - return s; - final int pathIdx = s.lastIndexOf(File.separatorChar); - return s.substring(pathIdx + 1, s.length()); - } - - /** - * Get a unique file name for an image in the game module archive. This function - * tests the provided name against those that are already present in the archive - * and if the file name already exists, proposes an alternate, unique file name. - * If an alternate is provided, it is of the form <code><tt>name</tt> + "(<tt>n</tt>)"</code> - * where <tt>n</tt> is an integer. - */ - public static String getUniqueImageFileName(String s) { - String t = s; - int index = 0; - final ArchiveWriter writer = GameModule.getGameModule().getArchiveWriter(); - while (writer.isImageAdded(DataArchive.IMAGE_DIR + t)) - t = s + '(' + (++index) + ')'; - return t; - } - - /** - * Read a base-250 2-byte big-endian word from a {@link #java.io.DataInputStream - * data input stream}. This is the default encoding for words imported modules. - */ - protected static int readBase250Word(DataInputStream in) throws IOException { - return in.readUnsignedByte() * 250 + in.readUnsignedByte(); - } - - /** - * Read a base-250 4-byte big-endian word from a {@link #java.io.DataInputStream - * data input stream}. This is the default encoding for integers in imported - * modules. - */ - protected static int readBase250Integer(DataInputStream in) throws IOException { - int result = in.readUnsignedByte(); - for (int i = 1; i < 4; ++i) - result = result*250 + in.readUnsignedByte(); - return result; - } - - /** - * Return a null-terminated string from an input stream. - */ - public static String readNullTerminatedString(InputStream in) throws IOException { - return readNullTerminatedString(in, 0); - } - - /** - * Read a null-terminated string from a file up to a maximum length including - * the null termination. If the actual string is longer, no more bytes will be read. - */ - public static String readNullTerminatedString(InputStream in, int maxLen) throws IOException { - final StringBuilder sb; - if (maxLen == 0) - sb = new StringBuilder(); - else - sb = new StringBuilder(maxLen); - char ch; - for (int i = 0; maxLen == 0 || i < maxLen; ++i) { - ch = (char) in.read(); - if (ch != 0) - sb.append(ch); - else - break; - } - return sb.toString(); - } - - /** - * Read a null-terminated string representing a Windows file name and convert - * Windows separator characters <tt>'\\'</tt> to the local separator character. - * This is the default file name format for imported modules and should be used - * whenever a filename is read as a null-terminated string in order to ensure - * platform independence. - */ - public static String readWindowsFileName(InputStream in) throws IOException { - final StringBuilder sb = new StringBuilder(); - char ch; - do { - ch = (char) in.read(); - if (ch == '\\') - sb.append(File.separatorChar); - else if (ch != 0) - sb.append(ch); - } while (ch != 0); - return sb.toString(); - } - - /** - * Return a file name without the extension. - */ - public static String stripExtension(String s) { - if (s.equals(".") || s.equals("..")) - return s; - final int index = s.lastIndexOf('.'); - final int pathIdx = s.lastIndexOf(File.separatorChar); - if (index == -1 || index < pathIdx) - return s; - else - return s.substring(0, index); - } - - public ImportModuleAction(Component comp) { - super(comp); - putValue(NAME, Resources.getString("Main.import_module")); - } - - /** - * A simple file filter that does a case-insensitive match against a file name - * extension. - */ - public static class FileExtensionFilter extends FileFilter { - private final String extension; - private final String description; - - /** - * @param extension file extension - * @param description a brief description (<it>e.g.</it>, <tt>"Bitmaps"</tt>). - */ - public FileExtensionFilter(String extension, String description) { - this.extension = extension; - this.description = description; - } - - public boolean accept(File f) { - if (f.isDirectory()) - return true; - if (getExtension(f.getName()).equalsIgnoreCase(extension)) - return true; - else - return false; - } - - public String getDescription() { - return description + " (*." + extension.toLowerCase() + ";*." + extension.toUpperCase() + ')'; - } - } - - /** - * Game piece and terrain symbols. - * - * @author Michael Kiefte - * - */ - static class SymbolSet { - - enum Shape { - SQUARE, HEX - } - - /** - * Contains all of the information for a single game piece or terrain icon - */ - class SymbolData { - /** - * Shared bitmap of all symbols in either the terrain or game piece set. - * Cannot be static as there are three different possible shared images. - */ - private final BufferedImage bitmap; - - /** - * Actual name of file in archive. May not be the same as the name - * provided in the configuration file if duplicates exist. - */ - private String fileName; - - /** - * Actual image which is lazily generated on request. - */ - private BufferedImage img; - - /** - * Prevents infinite loops when applying masks to symbol images. - */ - private final boolean isMask; - - /** - * Base 1 index into SymbolData array of masks. 0 means no mask. - */ - private int maskIndex; - - /** - * Actual name given in the configuration file--not the archive file name. - */ - private String name; - - /** - * Rectangle in shared bitmap <code>img</code>. - */ - private Rectangle rect; - - SymbolData(BufferedImage bitmap, boolean isMask) { - this.bitmap = bitmap; - this.isMask = isMask; - } - - /** - * Get the mask associated with this symbol. - */ - SymbolData getMask() { - if (ignoreMask || isMask) - return null; - // mask index is base 1 - else if (maskIndex > 0 && maskIndex <= maskData.length) - return maskData[maskIndex - 1]; - else - return null; - } - - /** - * Read symbol data from configuration file. - * - * @return <code>this</code>. - */ - SymbolData read(DataInputStream in) throws IOException { - name = readNullTerminatedString(in); - - // Have to read in all the zoom level sizes for the first one - // so that zooming in VASSAL can approximate the zoom behaviour of the - // imported module. - boolean readAllZoomLevels = false; - if (mapBoardSymbolSize == null) { - mapBoardSymbolSize = new int[3]; - readAllZoomLevels = true; - } - - // Alternate indexing style: - // if (header == -3) - // pict.maskIndex = in.readUnsignedByte(); - // else - maskIndex = readBase250Word(in); - for (int i = 0; i < 3; ++i) { - int x1 = in.readInt(); - int y1 = in.readInt(); - int x2 = in.readInt(); - int y2 = in.readInt(); - int width = x2 - x1 + 1; - int height = y2 - y1 + 1; - if (readAllZoomLevels) - mapBoardSymbolSize[i] = height; // or width - if (i == zoomLevel) - rect = new Rectangle(x1, y1, width, height); - } - return this; - } - - /** - * Returns the archive file name corresponding to the image for this - * symbol. If called, will actually write the image to the archive in - * order to get the file name itself. - * - * @throws IOException if unable to read the image files for this symbol. - */ - String getFileName() throws IOException { - if (fileName == null) - fileName = writeToArchive(); - return fileName; - } - - /** - * Get image corresponding to this symbol. Generates the image and applies - * optional mask if not already done so. - */ - BufferedImage getImage() { - if (img == null) { - img = bitmap.getSubimage(rect.x, rect.y, rect.width, rect.height); - if (getMask() != null) { - final BufferedImage bi = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_ARGB); - final Graphics2D g = bi.createGraphics(); - g.drawImage(img, null, 0, 0); - g.setComposite(AlphaComposite.DstAtop); - g.drawImage(getMask().getImage(), null, 0, 0); - img = bi; - } - } - return img; - } - - /** - * Write symbol image to archive and return archive file name. Will only - * write to archive once. - */ - String writeToArchive() throws IOException { - // only gets written once. if filename is not null, then we've - // already been written - if (fileName == null) { - fileName = getUniqueImageFileName(name); - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - ImageIO.write(getImage(), "png", out); - GameModule.getGameModule().getArchiveWriter().addImage(fileName, out.toByteArray()); - } - return fileName; - } - } - - // Permute negative of red band (doesn't matter which colour) to alpha band. - // masks are originally black where alpha should be 1.0 and white where - // alpha should be 0.0. - private final static float[][] THREE_BAND_MATRIX = { - { 0.0f, 0.0f, 0.0f, 0.0f }, - { 0.0f, 0.0f, 0.0f, 0.0f }, - { 0.0f, 0.0f, 0.0f, 0.0f }, - { -1.0f, 0.0f, 0.0f, 255.0f }}; - private final static float[][] ONE_BAND_MATRIX = { - { 0.0f, 0.0f }, - { 0.0f, 0.0f }, - { 0.0f, 0.0f }, - { -255.0f, 255.0f }}; - - /** - * Convert a black-and-white bitmap to a mask image. - */ - private static BufferedImage generateAlphaMask(BufferedImage img) { - final BandCombineOp op; - if (img.getSampleModel().getNumBands() == 1) - op = new BandCombineOp(ONE_BAND_MATRIX, null); - else - op = new BandCombineOp(THREE_BAND_MATRIX, null); - final BufferedImage bi = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB); - op.filter(img.getRaster(), bi.getRaster()); - return bi; - } - - // needed so that we can call file selection dialog - private final ImportModuleAction action; - - /** - * Unit symbols - */ - private SymbolData[] gamePieceData; - - /** - * Terrain symbols - */ - private SymbolData[] mapBoardData; - - /** - * Symbol size for all three zoom levels. Used to set zoom scale in VASSAL. - */ - private int[] mapBoardSymbolSize; - - /** - * Mask symbols. Only used by SymbolData when requesting a mask index. - * Doesn't get used at all in Version I sets - * (where <code>ignoreMask</code> is true). - */ - private SymbolData[] maskData = null; - - /** - * Hex or square. - */ - private Shape symbolShape; - - /** - * Zoom level to import - */ - private final int zoomLevel; - - /** - * Ignore mask despite specified mask indeces. True for older configuration files. - */ - private boolean ignoreMask; - - SymbolSet(ImportModuleAction action, int zoomLevel) { - this.action = action; - this.zoomLevel = zoomLevel; - } - - /** - * Read symbol images based on basename and suffix. Bitmap filenames are of the form - * <tt>name + "-CN.bmp"</tt> where C is the specified suffix ('M' for masks; 'U' for - * game pieces; 'T' for terrain symbols) and N is the base-1 zoom level. - * - * @param filename base file name. Should be the same as the base file name of the - * symbol set file. - * @param suffix single character suffix - */ - BufferedImage loadSymbolImage(String filename, char suffix) throws IOException { - final String fn = filename + '-' + suffix + (zoomLevel + 1) + ".bmp"; - File f = action.getCaseInsensitiveFile(new File(fn), bitmapFileFilter); - if (f == null) - throw new FileFormatException("Missing bitmap file: " + fn); - return ImageIO.read(f); - } - - /** - * Read dimensions from input file. The dimensions must occur in triplets in the - * configuration file--each one corresponding to a zoom level. - * The dimension returned corresponds to the requested zoom level. - */ - Dimension readDimension(DataInputStream in) throws IOException { - Dimension d = null; - for (int i = 0; i < 3; ++i) { - int width = in.readInt(); - int height = in.readInt(); - if (zoomLevel == i) - d = new Dimension(width, height); - } - return d; - } - - /** - * Returns the <code>SymbolData</code> corresponding to the game piece at - * the specified index. - * - * @return <code>null</code> if the index is out of - * bounds. - */ - SymbolData getGamePiece(int index) { - if (index >= 0 && index < gamePieceData.length) - return gamePieceData[index]; - else - return null; - } - - /** - * Returns the <code>SymbolData</code> corresponding to the terrain symbol at - * the specified index. - * - * @return <code>null</code> if the index is out of - * bounds. - */ - SymbolData getMapBoardSymbol(int index) { - if (index >= 0 & index < mapBoardData.length) - return mapBoardData[index]; - else - return null; - } - - /** - * @return The most frequently occuring dimension for game pieces in this module. - */ - Dimension getModalSize() { - final HashMap<Dimension, Integer> histogram = new HashMap<Dimension, Integer>(); - for (SymbolData piece : gamePieceData) { - final BufferedImage im = piece.getImage(); - final Dimension d = new Dimension(im.getWidth(), im.getHeight()); - final Integer i = histogram.get(d); - if (i == null) - histogram.put(d, 1); - else - histogram.put(d, i+1); - } - int max = 0; - final Dimension maxDim = new Dimension(0,0); - for (Dimension d : histogram.keySet()) { - int n = histogram.get(d); - if (n > max) { - max = n; - maxDim.height = d.height; - maxDim.width = d.width; - } - } - return maxDim; - } - - /** - * @return Map board symbol size corresponding to the current zoom level. - */ - int getMapBoardSymbolSize() { - return getMapBoardSymbolSize(zoomLevel); - } - - /** - * @return Map board symbol size corresponding to the specified zoom level - */ - int getMapBoardSymbolSize(int zoomLevel) { - return mapBoardSymbolSize[zoomLevel]; - } - - /** - * @return Hex or square? - */ - Shape getMapBoardSymbolShape() { - return symbolShape; - } - - /** - * Read a symbol set from the specified file. - */ - SymbolSet read(File f) throws IOException { - DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(f))); - - // if header is -3, then mask indeces are one-byte long. Otherwise, if - // the header - // is anything between -6 and -1, then mask indeces are base-250 - // two-byte short ints. - int header = in.readByte(); - if (header < -6 || header > -1) - throw new FileFormatException("Invalid Symbol Set Header"); - // Right now we could handle older set file formats, but I don't know what - // the map file is supposed to look like. Easier to upgrade format before - // importing. - // TODO: take care of older version symbol set files. - if (header == -3) - throw new FileFormatException("Symbol set file version less than 2.12\nConvert before importing."); - - /* int orientation = */ - in.readByte(); // 1=vertical; 2=horizontal; 3=grid - int mapStyle = in.readByte(); // 0=grid; 1=hex - - switch (mapStyle) { - case 1: - symbolShape = Shape.HEX; - break; - default: - symbolShape = Shape.SQUARE; - } - - int symSetVersion = in.readByte(); // 1=version 1 - switch (symSetVersion) { - case 0: - ignoreMask = false; - break; - default: - ignoreMask = true; - } - - int nMapBoardSymbols = readBase250Word(in); - /* terrainBMDims = */ - readDimension(in); - int nGamePieceSymbols = readBase250Word(in); - /* unitBMDims = */ - readDimension(in); - int nMasks = readBase250Word(in); - /* maskBMDims = */ - readDimension(in); - - // load images - String baseName = stripExtension(f.getPath()); - - BufferedImage mapBoardImages = loadSymbolImage(baseName, 't'); - mapBoardData = new SymbolData[nMapBoardSymbols]; - - for (int i = 0; i < nMapBoardSymbols; ++i) { - mapBoardData[i] = new SymbolData(mapBoardImages, false).read(in); - // check for size consistency. Not sure what to do if they're not - // all the same size - if (mapBoardData[i].rect.height != mapBoardData[0].rect.height - || mapBoardData[i].rect.width != mapBoardData[0].rect.width) - throw new FileFormatException("Map board image dimensions are inconsistent"); - if (mapBoardData[i].rect.width != mapBoardData[i].rect.height) - throw new FileFormatException("Map board image dimensions are not square"); - } - - BufferedImage gamePieceImages = loadSymbolImage(baseName, 'u'); - gamePieceData = new SymbolData[nGamePieceSymbols]; - for (int i = 0; i < nGamePieceSymbols; ++i) - gamePieceData[i] = new SymbolData(gamePieceImages, false).read(in); - - if (!ignoreMask) { - BufferedImage maskImages = loadSymbolImage(baseName, 'm'); - // convert binary bitmap to RGBA alpha mask - maskImages = generateAlphaMask(maskImages); - - maskData = new SymbolData[nMasks]; - for (int i = 0; i < nMasks; ++i) - maskData[i] = new SymbolData(maskImages, true).read(in); - } - - return this; - } - - /** - * Write all of the game pieces to the archive. Mainly for testing or if only - * the symbol set is imported. - */ - void writeToArchive() throws IOException { - for (SymbolData piece : gamePieceData) - piece.writeToArchive(); - } - } - - /** - * The map board itself. - * - * @author Michael Kiefte - * - */ - public static class MapBoard { - - /** - * A layout consisting of squares in a checkerboard pattern (<it>i.e.</it> each - * square has four neighbours). - * - * @author Michael Kiefte - * - */ - class GridLayout extends Layout { - - GridLayout(int size, int columns, int rows) { - super(size, columns, rows); - } - - @Override - Point coordinatesToPosition(int x, int y, boolean nullIfOffBoard) { - if (!nullIfOffBoard || onMapBoard(x, y)) { - int xx = getDeltaX() * x; - int yy = getDeltaY() * y; - return new Point(xx, yy); - } else - return null; - } - - @Override - Dimension getBoardSize() { - Dimension d = new Dimension(); - d.width = getDeltaX() * nColumns; - d.height = getDeltaY() * nRows; - return d; - } - - @Override - int getDeltaX() { - return getSize(); - } - - @Override - int getDeltaY() { - return getSize(); - } - - @Override - Point getOrigin() { - return new Point(getSize() / 2, getSize() / 2); - } - - @Override - SquareGrid getGeometricGrid() { - SquareGrid grid = new SquareGrid(); - grid.setOrigin(getOrigin()); - grid.setDx(getDeltaX()); - grid.setDy(getDeltaY()); - - return grid; - } - - @Override - Rectangle getRectangle(MapSheet map) { - Rectangle r = map.getField(); - - Point upperLeft = coordinatesToPosition(r.x, r.y, false); - Point lowerRight = coordinatesToPosition(r.x + r.width - 1, r.y - + r.height - 1, false); - - // get lower right-hand corner of lower right-hand square - lowerRight.x += getSize() - 1; - lowerRight.y += getSize() - 1; - - constrainRectangle(upperLeft, lowerRight); - - return new Rectangle(upperLeft.x, upperLeft.y, lowerRight.x - - upperLeft.x + 1, lowerRight.y - upperLeft.y + 1); - } - - @Override - RegularGridNumbering getGridNumbering() { - return new SquareGridNumbering(); - } - - @Override - int getNFaces() { - return 4; - } - } - - /** - * Redundant information about each hex. So far only used for determining - * the default order of line definitions for hex sides and hex lines. - * - * @author Michael Kiefte - * - */ - class Hex { - ArrayList<Line> hexLines = new ArrayList<Line>(); - - ArrayList<Line> hexSides = new ArrayList<Line>(); - } - - // hexagonal mapboard symbol - class HexData extends MapDrawable { - - protected final SymbolSet.SymbolData symbol; - - HexData(int index, SymbolSet.SymbolData symbol) { - super(index); - assert (symbol != null); - this.symbol = symbol; - } - - void draw(Graphics2D g) { - Point p = getPosition(); - if (symbol != null) - g.drawImage(symbol.getImage(), null, p.x, p.y); - } - } - - class MapBoardOverlay extends HexData { - - @Override - void draw(Graphics2D g) { - if (symbol != null) { - for (int y = 0; y < getNRows(); ++y) { - for (int x = 0; x < getNColumns(); ++x) { - Point p = coordinatesToPosition(x, y); - g.drawImage(symbol.getImage(), null, p.x, p.y); - } - } - } - } - - MapBoardOverlay(ImportModuleAction.SymbolSet.SymbolData symbol) { - super(-1, symbol); - } - } - - class HexLine extends Line { - - @Override - protected int compare(LineDefinition o1, LineDefinition o2) { - if (o1 == null && o2 == null) - return 0; - else if (o1 == null) - return 1; - else if (o2 == null) - return -1; - int priority = o1.getHexLineDrawPriority() - o2.getHexLineDrawPriority(); - if (priority != 0) - return priority; - else - return super.compare(o1, o2); - } - - private final int direction; - - HexLine(int index, int line, int direction) { - super(index, line); - this.direction = direction; - } - - @Override - void draw(Graphics2D g) { - LineDefinition l = getLine(); - - if (l != null) { - Point pos = getPosition(); - int size = getLayout().getSize(); - pos.translate(size / 2, size / 2); - Layout lo = getLayout(); - - // if ((direction & 0x1) > 0) // horizontal north west - // l.addLine(cX, cY, cX-(dX/2+evenFudgeX)/2, cY-dY/2); - if ((direction & 0x6) > 0) {// north west 0x4 = version 1 - Point nw = lo.getNorthWest(hexIndex); - nw.translate(size / 2, size / 2); - l.addLine(pos.x, pos.y, (pos.x + nw.x) / 2.0f, - (pos.y + nw.y) / 2.0f); - } - if ((direction & 0x8) > 0) {// west - Point w = lo.getWest(hexIndex); - w.translate(size / 2, size / 2); - l.addLine(pos.x, pos.y, (pos.x + w.x) / 2.0f, - (pos.y + w.y) / 2.0f); - } - if ((direction & 0x30) > 0) { // south west 0x10 = version 1 - Point sw = lo.getSouthWest(hexIndex); - sw.translate(size / 2, size / 2); - l.addLine(pos.x, pos.y, (pos.x + sw.x) / 2.0f, - (pos.y + sw.y) / 2.0f); - } - // if ((direction & 0x40) > 0) // horizontal south west - // l.addLine(cX, cY, cX-(dX/2+evenFudgeX)/2, cY+dY/2); - if ((direction & 0x80) > 0) {// south - Point s = lo.getSouth(hexIndex); - s.translate(size / 2, size / 2); - l.addLine(pos.x, pos.y, (pos.x + s.x) / 2.0f, - (pos.y + s.y) / 2.0f); - } - if ((direction & 0x100) > 0) {// north - Point n = lo.getNorth(hexIndex); - n.translate(size / 2, size / 2); - l.addLine(pos.x, pos.y, (pos.x + n.x) / 2.0f, - (pos.y + n.y) / 2.0f); - } - // if ((direction & 0x200) > 0) // horizontal north east - // l.addLine(cX, cY, cX+(dX/2+oddFudgeX)/2, cY-dY/2); - if ((direction & 0xC00) > 0) { // north east 0x800 = version 1 - Point ne = lo.getNorthEast(hexIndex); - ne.translate(size / 2, size / 2); - l.addLine(pos.x, pos.y, (pos.x + ne.x) / 2.0f, - (pos.y + ne.y) / 2.0f); - } - if ((direction & 0x1000) > 0) {// east - Point e = lo.getEast(hexIndex); - e.translate(size / 2, size / 2); - l.addLine(pos.x, pos.y, (pos.x + e.x) / 2.0f, - (pos.y + e.y) / 2.0f); - } - if ((direction & 0x6000) > 0) { // south east 0x2000 = version 1 - Point se = lo.getSouthEast(hexIndex); - se.translate(size / 2, size / 2); - l.addLine(pos.x, pos.y, (pos.x + se.x) / 2.0f, - (pos.y + se.y) / 2.0f); - } - // if ((direction & 0x8000) > 0) // horizontal south east - // l.addLine(cX, cY, cX+(dX/2+oddFudgeX)/2, cY+dY/2); - } - - if (this == hexLines.get(hexLines.size() - 1)) - drawLines(g, BasicStroke.CAP_BUTT); - } - - @Override - ArrayList<Line> getLineList(Hex h) { - return h.hexLines; - } - } - - class HexSide extends Line { - - @Override - protected int compare(LineDefinition o1, LineDefinition o2) { - if (o1 == null && o2 == null) - return 0; - else if (o1 == null) - return 1; - else if (o2 == null) - return -1; - int priority = o1.getHexSideDrawPriority() - o2.getHexSideDrawPriority(); - if (priority != 0) - return priority; - else - return super.compare(o1, o2); - } - - // flags indicating which side to draw. - private final int side; - - HexSide(int index, int line, int side) { - super(index, line); - this.side = side; - } - - @Override - void draw(Graphics2D g) { - - LineDefinition l = getLine(); - - if (l != null) { - Point p = getPosition(); - int size = getLayout().getSize(); - int dX = getLayout().getDeltaX(); - int dY = getLayout().getDeltaY(); - - if ((side & 0x1) > 0) { // vertical SW - Point sw = getLayout().getSouthWest(hexIndex); - sw.translate(dX, 0); - Point s = getLayout().getSouth(hexIndex); - l.addLine(p.x, sw.y, p.x + (size / 5), s.y); - } - if ((side & 0x2) > 0) { // vertical NW - Point sw = getLayout().getSouthWest(hexIndex); - sw.translate(dX, 0); - l.addLine(p.x, sw.y, p.x + (size / 5), p.y); - } - if ((side & 0x4) > 0) { // vertical N - l.addLine(p.x + (size / 5), p.y, p.x + dX, p.y); - } - if ((side & 0x8) > 0) { // horizontal SW - Point se = getLayout().getSouthEast(hexIndex); - l.addLine(p.x, p.y + dY, se.x, p.y + dY + (size / 5)); - } - if ((side & 0x10) > 0) { // horizontal W - l.addLine(p.x, p.y + (size / 5), p.x, p.y + dY); - } - if ((side & 0x20) > 0) { // horizontal NW - Point ne = getLayout().getNorthEast(hexIndex); - l.addLine(p.x, p.y + (size / 5), ne.x, p.y); - } - if ((side & 0x40) > 0) { // square left - l.addLine(p.x, p.y, p.x, p.y + dY); - } - if ((side & 0x80) > 0) { // square top - l.addLine(p.x, p.y, p.x + dX, p.y); - } - } - - - if (this == hexSides.get(hexSides.size() - 1)) - drawLines(g, BasicStroke.CAP_ROUND); - } - - @Override - ArrayList<Line> getLineList(Hex h) { - return h.hexSides; - } - } - - class HorizontalHexLayout extends HorizontalLayout { - - HorizontalHexLayout(int size, int columns, int rows) { - super(size, columns, rows); - } - - @Override - Dimension getBoardSize() { - Dimension d = new Dimension(); - d.width = getDeltaX() * nColumns + getSize() / 2; - d.height = getDeltaY() * nRows + getSize() / 5 + 1; - return d; - } - - @Override - int getDeltaX() { - return getSize() - (isPreV208Layout()?2:0); - } - - @Override - int getDeltaY() { - return getSize() * 4 / 5 - 1; - } - - @Override - Point getOrigin() { - return new Point(getSize() / 2, getSize() / 2 - (isPreV208Layout() ? 1 : 0)); - } - - @Override - HexGrid getGeometricGrid() { - HexGrid mg = new HexGrid(); - - mg.setSideways(true); - - // VASSAL defines these sideways. Height always refers to the major - // dimension, - // and Dy always refers to height whether they're sideways or not. - mg.setOrigin(getOrigin()); - mg.setDy(getDeltaX()); - mg.setDx(getDeltaY()); - - return mg; - } - } - - /** - * A layout consisting of squares in which every second row is shifted to the - * right by one half-width. Used to approximate hexagons as each square has - * six neighbours. - * - * @author Michael Kiefte - * - */ - class GridOffsetRowLayout extends HorizontalLayout { - - GridOffsetRowLayout(int size, int columns, int rows) { - super(size, columns, rows); - } - - @Override - Dimension getBoardSize() { - Dimension d = new Dimension(); - d.height = getDeltaY() * nRows + 1; - d.width = getDeltaX() * nColumns + getSize()/2 + 1; - return d; - } - - @Override - int getDeltaX() { - return getSize(); - } - - @Override - int getDeltaY() { - return getSize(); - } - - @Override - Point getOrigin() { - return new Point(getSize() * 7 / 12, getSize() / 2); - } - - @Override - AbstractConfigurable getGeometricGrid() { - HexGrid mg = new HexGrid(); - - mg.setSideways(true); - - mg.setOrigin(getOrigin()); - mg.setDx(getDeltaY()); - mg.setDy(getDeltaX()); - - return mg; - } - } - - /** - * A layout in which every second row is offset. - * - * @author Michael Kiefte - * - */ - abstract class HorizontalLayout extends Layout { - - @Override - int getNFaces() { - return 6; - } - - @Override - void setGridNumberingOffsets(RegularGridNumbering numbering, MapSheet sheet) { - Point position = coordinatesToPosition(sheet.getField().x, sheet.getField().y, true); - position.translate(getDeltaX()/2, getDeltaY()/2); - int rowOffset = numbering.getColumn(position); - int colOffset = numbering.getRow(position); - - rowOffset = -rowOffset + sheet.getTopLeftRow(); - colOffset = -colOffset + sheet.getTopLeftCol(); - - numbering.setAttribute(RegularGridNumbering.H_OFF, rowOffset); - numbering.setAttribute(RegularGridNumbering.V_OFF, colOffset); - } - - @Override - void initGridNumbering(RegularGridNumbering numbering, MapSheet sheet) { - // TODO: staggering doesn't appear to be handled at all! - super.initGridNumbering(numbering, sheet); - numbering.setAttribute(RegularGridNumbering.FIRST, sheet.rowsAndCols() ? 'H' : 'V'); - numbering.setAttribute(RegularGridNumbering.H_TYPE, sheet.numericRows() ? 'N' : 'A'); - numbering.setAttribute(RegularGridNumbering.V_TYPE, sheet.numericCols() ? 'N' : 'A'); - } - - HorizontalLayout(int size, int columns, int rows) { - super(size, columns, rows); - } - - @Override - HexGridNumbering getGridNumbering() { - return new HexGridNumbering(); - } - - @Override - Point coordinatesToPosition(int x, int y, boolean nullIfOffBoard) { - if (!nullIfOffBoard || onMapBoard(x, y)) { - int xx = getDeltaX() * x + (y % 2) * getDeltaX() / 2; - int yy = getDeltaY() * y; - return new Point(xx, yy); - } else - return null; - } - - @Override - Point getNorthEast(int index) { - return indexToPosition(index - nColumns + (index / nColumns) % 2, false); - } - - @Override - Point getNorthWest(int index) { - return indexToPosition(index - nColumns - (index / nColumns + 1) % 2, false); - } - - @Override - Rectangle getRectangle(MapSheet map) { - Rectangle r = map.getField(); - - Point upperLeft = coordinatesToPosition(r.x, r.y, false); - Point lowerRight = coordinatesToPosition(r.x + r.width - 1, r.y - + r.height - 1, false); - - // adjust for staggering of hexes - if (map.firstHexLeft()) // next one down is to the left - upperLeft.x -= getSize() / 2; - - // adjust x of bottom right-hand corner - if (r.y % 2 == (r.y + r.height - 1) % 2) { // both even or both odd - if (map.firstHexRight()) - lowerRight.x += getSize() / 2; - // check to see if lower right-hand corner is on the wrong - // square - } - else if (r.y % 2 == 1) { - // top is odd and bottom is even - if (map.firstHexLeft()) - lowerRight.x += getSize() / 2; - else - lowerRight.x += getSize(); - } - else if (map.firstHexLeft() && r.y % 2 == 0) - // top is even and bottom is odd - lowerRight.x -= getSize() / 2; - - // get lower right corner of lower right hex - lowerRight.x += getSize() - 1; - lowerRight.y += getSize() - 1; - - // adjust so that we don't overlap the centres of hexes that don't - // belong to this sheet - upperLeft.x += getSize() / 5; - lowerRight.x -= getSize() / 5; - - constrainRectangle(upperLeft, lowerRight); - - return new Rectangle(upperLeft.x, upperLeft.y, lowerRight.x - - upperLeft.x + 1, lowerRight.y - upperLeft.y + 1); - } - - @Override - Point getSouthEast(int index) { - return indexToPosition(index + nColumns + (index / nColumns) % 2, false); - } - - @Override - Point getSouthWest(int index) { - return indexToPosition(index + nColumns - (index / nColumns + 1) % 2, false); - } - } - - abstract class Line extends MapDrawable { - - // index of line definition. don't know the actual line definitions - // until later - private final int line; - - Line(int index, int line) { - super(index); - this.line = line; - if (hexes == null) - hexes = new Hex[getNColumns() * getNRows()]; - if (hexes[index] == null) - hexes[index] = new Hex(); - getLineList(hexes[index]).add(this); - } - - LineDefinition getLine() { - return getLineDefinition(line); - } - - abstract ArrayList<Line> getLineList(Hex h); - - protected int compare(LineDefinition o1, LineDefinition o2) { - if (o1 == null && o2 == null) - return 0; - else if (o1 == null) - return 1; - else if (o2 == null) - return -1; - // go through all the hexes - // and determine file order for lines - for (Hex h : hexes) { - if (h == null) - continue; - boolean index1 = false; - boolean index2 = false; - for (Line hl : getLineList(h)) { - if (hl.getLine() == o1) { - if (index2) - return 1; - index1 = true; - } else if (hl.getLine() == o2) { - if (index1) - return -1; - index2 = true; - } - } - } - return 0; - } - - protected void drawLines(Graphics2D g, int cap) { - ArrayList<LineDefinition> lds = new ArrayList<LineDefinition>(lineDefinitions.length); - lds.addAll(Arrays.asList(lineDefinitions)); - - // find the next line in priority - while (lds.size() > 0) { - LineDefinition lowest = null; - for (LineDefinition ld : lds) { - if (ld == null) - continue; - else if (lowest == null || compare(ld, lowest) < 0) - lowest = ld; - } - if (lowest == null) - break; - else { - lowest.draw(g, cap); - lowest.clearPoints(); - lds.remove(lowest); - } - } - } - } - - // for hex sides and lines - class LineDefinition { - - private final Color color; - - private int hexLineDrawPriority = 0; - - private int hexSideDrawPriority = 0; - - private ArrayList<ArrayList<Point2D.Float>> points = new ArrayList<ArrayList<Point2D.Float>>(); - - // width - private final int size; - - private final LineStyle style; - - LineDefinition(Color color, int size, LineStyle style) { - this.color = color; - this.size = size; - this.style = style; - } - - private void setHexLineDrawPriority(int priority) { - // only change the priority if it hasn't already been set. - if (hexLineDrawPriority == 0) - hexLineDrawPriority = priority; - } - - private void setHexSideDrawPriority(int priority) { - if (hexSideDrawPriority == 0) - hexSideDrawPriority = priority; - } - - Color getColor() { - return color; - } - - BasicStroke getStroke(int cap) { - if (size <= 0 || style == null) - return null; - return style.getStroke(size, cap); - } - - void addLine(float x1, float y1, float x2, float y2) { - addLine(new Point2D.Float(x1, y1), new Point2D.Float(x2, y2)); - } - - void addLine(int x1, int y1, float x2, float y2) { - addLine(new Point2D.Float((float) x1, (float) y1), - new Point2D.Float(x2, y2)); - } - - void addLine(int x1, int y1, int x2, int y2) { - addLine(new Point2D.Float((float) x1, (float) y1), - new Point2D.Float((float) x2, (float) y2)); - } - - void addLine(Point2D.Float a, Point2D.Float b) { - // find out if this line is attached to any other line in the list. - // if not create a line. - for (int i = 0; i < points.size(); ++i) { - ArrayList<Point2D.Float> lineA = points.get(i); - if (a.equals(lineA.get(0))) { // a at the start of lineA - // repeated segment? - if (b.equals(lineA.get(1))) - return; - // find out if this segment joins two lines already in - // existance - for (int j = 0; j < points.size(); ++j) { - if (i == j) - continue; - ArrayList<Point2D.Float> lineB = points.get(j); - if (b.equals(lineB.get(0))) { // point A at start of - // lineA and point B at - // start of lineB - if (lineA.size() < lineB.size()) { // insert A - // before B - for (int k = 0; k < lineA.size(); ++k) - lineB.add(0, lineA.get(k)); - points.remove(i); - } else { // insert B before A - for (int k = 0; k < lineB.size(); ++k) - lineA.add(0, lineB.get(k)); - points.remove(j); - } - return; - } else if (b.equals(lineB.get(lineB.size() - 1))) { - // point A at start of lineA and point B at end of lineB - lineB.addAll(lineA); - points.remove(i); - return; - } - } - // point A at start of lineA and point B is open - lineA.add(0, b); - return; - } else if (a.equals(lineA.get(lineA.size() - 1))) { - // Point A is at end of line A - // repeated segment? - if (b.equals(lineA.get(lineA.size() - 2))) - return; - for (int j = 0; j < points.size(); ++j) { - if (i == j) // skip closed loops - continue; - ArrayList<Point2D.Float> lineB = points.get(j); - if (b.equals(lineB.get(0))) { - // point A at end of line A and point B at start of lineB - lineA.addAll(lineB); - points.remove(j); - return; - } else if (b.equals(lineB.get(lineB.size() - 1))) { - // point A at end of lineA and point B at end of lineB - if (lineA.size() < lineB.size()) { // add line A to B - for (int k = lineA.size() - 1; k >= 0; --k) - lineB.add(lineA.get(k)); - points.remove(i); - } else { // add line B to A - for (int k = lineB.size() - 1; k >= 0; --k) - lineA.add(lineB.get(k)); - points.remove(j); - } - return; - } - } - // point A at the end of lineA and point B is open - lineA.add(b); - return; - } - // find out if the segment already exists - for (int j = 1; j < lineA.size() - 1; ++j) - if (a.equals(lineA.get(j)) - && (b.equals(lineA.get(j - 1)) || b.equals(lineA - .get(j + 1)))) - return; - } - - // point A is open - for (ArrayList<Point2D.Float> line : points) { - if (b.equals(line.get(0))) { // B at the start of the line - // repeated segment? - if (a.equals(line.get(1))) - return; - line.add(0, a); - return; - } else if (b.equals(line.get(line.size() - 1))) { - // B at the end of the line - if (a.equals(line.get(line.size() - 2))) - return; - line.add(a); - return; - } - } - - // both A and B are open - ArrayList<Point2D.Float> newLine = new ArrayList<Point2D.Float>(2); - newLine.add(a); - newLine.add(b); - points.add(newLine); - } - - void clearPoints() { - points.clear(); - } - - void draw(Graphics2D g, int cap) { - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); - BasicStroke stroke = getStroke(cap); - if (stroke == null) - return; - g.setStroke(stroke); - g.setColor(getColor()); - GeneralPath gp = new GeneralPath(GeneralPath.WIND_EVEN_ODD); - for (ArrayList<Point2D.Float> line : points) { - gp.moveTo(line.get(0).x, line.get(0).y); - for (Point2D.Float p : line) { - if (!p.equals(line.get(0))) - gp.lineTo(p.x, p.y); - else if (p != line.get(0)) - gp.closePath(); - } - } - g.draw(gp); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_OFF); - } - - int getHexLineDrawPriority() { - return hexLineDrawPriority; - } - - int getHexSideDrawPriority() { - return hexSideDrawPriority; - } - } - - enum LineStyle { - DASH_DOT(new float[] { 12.0f, 8.0f, 4.0f, 8.0f }), DASH_DOT_DOT( - new float[] { 12.f, 4.0f, 4.0f, 4.0f, 4.0f, 4.0f }), DASHED( - new float[] { 12.0f, 8.0f }), DOTTED(new float[] { 4.0f, 4.0f }), SOLID( - null); - - private float[] dash; - - LineStyle(float[] dash) { - this.dash = dash; - } - - BasicStroke getStroke(int size, int cap) { - if (dash == null) - return new BasicStroke(size, cap, - BasicStroke.JOIN_ROUND); - else - return new BasicStroke(size, BasicStroke.CAP_BUTT, - BasicStroke.JOIN_ROUND, 0.0f, dash, 0.0f); - } - } - - // anything that can be drawn on the map and is associated with a particular - // hex. - abstract class MapDrawable { - - // hex index in row-major order: determines location on map - protected final int hexIndex; - - MapDrawable(int index) { - this.hexIndex = index; - } - - abstract void draw(Graphics2D g); - - int getHexIndex() { - return hexIndex; - } - - // upper left-hand corner of hex or square - Point getPosition() { - return indexToPosition(hexIndex); - } - - // rectangle for hex or square. - Rectangle getRectangle() { - Rectangle r = new Rectangle(getPosition()); - int width = getLayout().getSize(); - r.width = width; - r.height = width; - return r; - } - } - - // for hexsides and hex lines - class MapSheet { - - private int topLeftCol = 0; - - private int topLeftRow = 0; - - private final Rectangle field; - - private final String name; - - private final int nColChars; - - private final int nRowChars; - - private final int style; - - private Zone zone; - - MapSheet(String name, Rectangle playingFieldPosition, int style, - int nColChars, int nRowChars) { - this.name = name; - this.field = playingFieldPosition; - this.style = style; - this.nColChars = nColChars; - this.nRowChars = nRowChars; - } - - Rectangle getField() { - return field; - } - - String getName() { - return name; - } - - int getNColChars() { - return nColChars; - } - - int getNRowChars() { - return nRowChars; - } - - String getRectangleAsString() { - Rectangle r = getLayout().getRectangle(this); - if (r == null) - return null; - return r.x + "," + r.y + ";" + (r.x + r.width - 1) + "," + r.y - + ";" + (r.x + r.width - 1) + "," + (r.y + r.height - 1) - + ";" + r.x + "," + (r.y + r.height - 1); - } - - boolean alphaCols() { - return !numericCols(); - } - - boolean alphaRows() { - return !numericRows(); - } - - boolean colsAndRows() { - return (style & 0x2) > 0; - } - - boolean colsIncreaseLeft() { - return !colsIncreaseRight(); - } - - boolean colsIncreaseRight() { - return (style & 0x10) > 0; - } - - boolean firstHexDown() { - return (style & 0x40) > 0 && getLayout() instanceof VerticalLayout; - } - - boolean firstHexLeft() { - return (style & 0x40) > 0 && getLayout() instanceof HorizontalLayout; - } - - boolean firstHexRight() { - return (style & 0x40) == 0 && getLayout() instanceof HorizontalLayout; - } - - boolean firstHexUp() { - return (style & 0x40) == 0 && getLayout() instanceof VerticalLayout; - } - - RegularGridNumbering getGridNumbering() { - // numbering system - RegularGridNumbering gn = getLayout().getGridNumbering(); - getLayout().initGridNumbering(gn, this); - return gn; - } - - Zone getZone() { - if (zone == null) { - zone = new Zone(); - zone.setConfigureName(getName()); - - String rect = getRectangleAsString(); - if (rect == null) - return null; - zone.setAttribute(Zone.PATH, rect); - zone.setAttribute(Zone.LOCATION_FORMAT, "$name$ $gridLocation$"); - AbstractConfigurable mg = getLayout().getGeometricGrid(); - - // add numbering system to grid - RegularGridNumbering gn = getGridNumbering(); - gn.addTo(mg); - mg.add(gn); - - // add grid to zone - mg.addTo(zone); - zone.add(mg); - - getLayout().setGridNumberingOffsets(gn, this); - } - - return zone; - } - - boolean numericCols() { - return (style & 0x4) > 0; - } - - boolean numericRows() { - return (style & 0x8) > 0; - } - - boolean rowsAndCols() { - return !colsAndRows(); - } - - boolean rowsIncreaseDown() { - return (style & 0x20) > 0; - } - - boolean rowsIncreaseUp() { - return !rowsIncreaseDown(); - } - - int getTopLeftCol() { - return topLeftCol; - } - - void setTopLeftCol(int topLeftCol) { - this.topLeftCol = topLeftCol; - } - - int getTopLeftRow() { - return topLeftRow; - } - - void setTopLeftRow(int topLeftRow) { - this.topLeftRow = topLeftRow; - } - } - - class PlaceName extends MapDrawable { - - // text colour - private final Color color; - - // bitmap flags - private final int font; - - // position relative to the hex. not really orientation. e.g., can't - // have vertical text. - private final PlaceNameOrientation orientation; - - // font size - private final int size; - - // the actual name - private final String text; - - PlaceName(int index, String text, Color color, - PlaceNameOrientation orientation, int size, int font) { - super(index); - this.text = text; - assert (color != null); - this.color = color; - assert (orientation != null); - this.orientation = orientation; - assert (size > 0); - this.size = size; - font &= 0x7f; - int fontIndex = font & 0xf; - if (fontIndex < 1 || fontIndex > 9) { - fontIndex = 9; - font = font & 0xf0 | fontIndex; - } - this.font = font; - } - - Font getFont() { - return getDefaultFont(getSize(), font); - } - - Point getPosition(Graphics2D g) { - Point p = getPosition(); - assert (g.getFont() == getFont()); - FontMetrics fm = g.getFontMetrics(); - int size = getLayout().getSize(); - - switch (orientation) { - case LOWER_CENTER: - case UPPER_CENTER: - case LOWER_RIGHT: - case UPPER_RIGHT: - case UPPER_LEFT: - case LOWER_LEFT: - case HEX_CENTER: - p.x += size / 2; // middle of the hex. - break; - case CENTER_RIGHT: - p.x += size; // right of hex - break; - case CENTER_LEFT: - break; - } - switch (orientation) { - case LOWER_CENTER: - case UPPER_CENTER: - case HEX_CENTER: - // text centered - p.x -= fm.charsWidth(text.toCharArray(), 0, text.length()) / 2; - break; - case UPPER_LEFT: - case LOWER_LEFT: - case CENTER_LEFT: - // right justified - p.x -= fm.charsWidth(text.toCharArray(), 0, text.length()); - break; - case LOWER_RIGHT: - case UPPER_RIGHT: - case CENTER_RIGHT: - break; - } - switch (orientation) { - case LOWER_CENTER: - case LOWER_RIGHT: - case LOWER_LEFT: - p.y += size + fm.getAscent(); - break; - case UPPER_CENTER: - case UPPER_RIGHT: - case UPPER_LEFT: - p.y -= fm.getDescent(); - break; - case CENTER_LEFT: - case CENTER_RIGHT: - case HEX_CENTER: - p.y += size / 2 + fm.getHeight() / 2 - fm.getDescent(); - break; - } - return p; - } - - // scale the size more appropriately - int getSize() { - return (size + 1) * 4 / 3 - 1; - } - - void draw(Graphics2D g) { - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); - g.setFont(getFont()); - g.setColor(color); - Point p = getPosition(g); - g.drawString(text, p.x, p.y); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_OFF); - } - } - - enum PlaceNameOrientation { - CENTER_LEFT, CENTER_RIGHT, HEX_CENTER, LOWER_CENTER, LOWER_LEFT, LOWER_RIGHT, UPPER_CENTER, UPPER_LEFT, UPPER_RIGHT; - } - - /** - * A layout consisting of squares in which every second column is shifted - * downward by one half width. This is done to approximate hexagons as each - * square as six neighbours. - * - * @author Michael Kiefte - * - */ - class GridOffsetColumnLayout extends VerticalLayout { - - GridOffsetColumnLayout(int size, int columns, int rows) { - super(size, columns, rows); - } - - @Override - Dimension getBoardSize() { - Dimension d = new Dimension(); - d.width = getDeltaX() * nColumns + 1; - d.height = getDeltaY() * nRows + getSize() / 2 + 1; - return d; - } - - @Override - int getDeltaX() { - return getSize(); - } - - @Override - int getDeltaY() { - return getSize(); - } - - @Override - Point getOrigin() { - return new Point(getSize() * 7 / 12, getSize() / 2); - } - - @Override - AbstractConfigurable getGeometricGrid() { - HexGrid mg = new HexGrid(); - - mg.setOrigin(getOrigin()); - mg.setDx(getDeltaX()); - mg.setDy(getDeltaY()); - - return mg; - } - } - - class VerticalHexLayout extends VerticalLayout { - - VerticalHexLayout(int size, int columns, int rows) { - super(size, columns, rows); - } - - @Override - Dimension getBoardSize() { - Dimension d = new Dimension(); - d.width = getDeltaX() * nColumns + getSize() / 5 + 1; - d.height = getDeltaY() * nRows + getSize() / 2 + 1; - return d; - } - - @Override - int getDeltaX() { - return getSize() * 4 / 5 - (isPreV208Layout() ? 1 : 0); - } - - @Override - int getDeltaY() { - return getSize() - (isPreV208Layout() ? 2 : 1); - } - - @Override - Point getOrigin() { - return new Point(getSize() / 2, getSize() / 2 - (isPreV208Layout() ? 1 : 0)); - } - - @Override - HexGrid getGeometricGrid() { - HexGrid mg = new HexGrid(); - - mg.setOrigin(getOrigin()); - mg.setDx(getDeltaX()); - mg.setDy(getDeltaY()); - - return mg; - } - } - - /** - * A layout in which every second column is offset. - * - * @author Michael Kiefte - * - */ - abstract class VerticalLayout extends Layout { - - @Override - int getNFaces() { - return 6; - } - - VerticalLayout(int size, int columns, int rows) { - super(size, columns, rows); - } - - @Override - HexGridNumbering getGridNumbering() { - return new HexGridNumbering(); - } - - @Override - void initGridNumbering(RegularGridNumbering numbering, MapSheet sheet) { - boolean stagger = false; - if (sheet.firstHexDown() && sheet.getField().x%2 == 1) - stagger = true; - else if (sheet.firstHexUp() && sheet.getField().x%2 == 0) - stagger = true; - numbering.setAttribute(HexGridNumbering.STAGGER, stagger); - super.initGridNumbering(numbering, sheet); - } - - @Override - Point coordinatesToPosition(int x, int y, boolean nullIfOffBoard) { - if (!nullIfOffBoard || onMapBoard(x, y)) { - int xx = getDeltaX() * x; - int yy = getDeltaY() * y + x % 2 * getDeltaY() / 2; - return new Point(xx, yy); - } else - return null; - } - - @Override - Point getNorthEast(int index) { - return indexToPosition(index - nColumns * ((index % nColumns + 1) % 2) + 1, false); - } - - @Override - Point getNorthWest(int index) { - return indexToPosition(index - nColumns * ((index % nColumns + 1) % 2) - 1, false); - } - - @Override - Rectangle getRectangle(MapSheet map) { - Rectangle r = map.getField(); - - if (r.width <= 0 || r.height <= 0) - return null; - - Point upperLeft = coordinatesToPosition(r.x, r.y, false); - Point lowerRight = coordinatesToPosition(r.x + r.width - 1, r.y - + r.height - 1, false); - - // adjust for staggering of hexes - if (map.firstHexUp()) // next one over is above - upperLeft.y -= getSize() / 2; - - // adjust y of bottom right-hand corner - if (r.x % 2 == (r.x + r.width - 1) % 2) { // both even or both odd - if (map.firstHexDown()) - lowerRight.y += getSize() / 2; - // check to see if lower right-hand corner is on the wrong - // square - } else if (r.x % 2 == 1) { - // left is odd and right is even - if (map.firstHexDown()) - lowerRight.y += getSize(); - ... [truncated message content] |