diff --git a/projects/atlanti/src/java/com/samskivert/atlanti/TileGeometryTest.java b/projects/atlanti/src/java/com/samskivert/atlanti/TileGeometryTest.java index bc8376fa..b0b54e97 100644 --- a/projects/atlanti/src/java/com/samskivert/atlanti/TileGeometryTest.java +++ b/projects/atlanti/src/java/com/samskivert/atlanti/TileGeometryTest.java @@ -1,14 +1,16 @@ // -// $Id: TileGeometryTest.java,v 1.2 2001/10/16 01:41:55 mdb Exp $ +// $Id: TileGeometryTest.java,v 1.3 2001/10/16 09:31:46 mdb Exp $ package com.threerings.venison; -import java.awt.*; -import javax.swing.*; -import java.util.ArrayList; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; + +import javax.swing.JFrame; +import javax.swing.JPanel; import com.samskivert.swing.util.SwingUtil; -import com.samskivert.util.IntTuple; /** * A simple class for testing the tile geometry specifications by drawing @@ -19,87 +21,18 @@ public class TileGeometryTest { public TileGeometryTest () { - ArrayList polys = new ArrayList(); - ArrayList colors = new ArrayList(); - - // create polygons from the various tile features - for (int i = 0; i < TileUtil.TILE_FEATURES.length; i++) { - // convert tile index into x and y coordinates (in tile - // feature coords which will be converted to screen coords) - int x = 4 * (i % 5), y = 4 * (i / 5); - - // the first feature is the background color - Object[] features = (Object[])TileUtil.TILE_FEATURES[i]; - IntTuple base = (IntTuple)features[0]; - - // add a polygon containing the whole tile colored with the - // background color - colors.add(COLOR_MAP[base.left]); - Polygon poly = new Polygon(); - poly.addPoint(((x + 0) * TILE_WIDTH) / 4, - ((y + 0) * TILE_HEIGHT) / 4); - poly.addPoint(((x + 4) * TILE_WIDTH) / 4, - ((y + 0) * TILE_HEIGHT) / 4); - poly.addPoint(((x + 4) * TILE_WIDTH) / 4, - ((y + 4) * TILE_HEIGHT) / 4); - poly.addPoint(((x + 0) * TILE_WIDTH) / 4, - ((y + 4) * TILE_HEIGHT) / 4); - polys.add(poly); - - // the remainder are tuple/coordinate pairs - for (int f = 1; f < features.length; f += 2) { - IntTuple type = (IntTuple)features[f]; - int[] coords = (int[])features[f+1]; - - // create a color for this polygon - colors.add(COLOR_MAP[type.left]); - - // if this is a road segment, we need to create a special - // polygon - if (type.left == ROAD) { - poly = TileUtil.roadSegmentToPolygon( - coords[0], coords[1], coords[2], coords[3]); - // translate the polygon into our coordinate space - poly.translate((x * TILE_WIDTH)/4, (y * TILE_HEIGHT)/4); - - } else { - // otherwise create the polygon directly from the coords - poly = new Polygon(); - for (int c = 0; c < coords.length; c += 2) { - // translate and scale the coords accordingly - int fx = ((x + coords[c]) * TILE_WIDTH) / 4; - int fy = ((y + coords[c+1]) * TILE_HEIGHT) / 4; - poly.addPoint(fx, fy); - } - } - - polys.add(poly); - } + for (int i = 0; i < TILE_TYPES; i++) { + _tiles[i] = new VenisonTile(i+1, false, NORTH, i % 5, i / 5); } - - // create our arrays - _polys = new Polygon[polys.size()]; - polys.toArray(_polys); - _colors = new Color[colors.size()]; - colors.toArray(_colors); } public void paintComponent (Graphics g) { super.paintComponent(g); - // paint our polygons - for (int i = 0; i < _polys.length; i++) { - g.setColor(_colors[i]); - g.fillPolygon(_polys[i]); - } - - // outline the tiles - g.setColor(Color.black); - for (int i = 0; i < 20; i++) { - int x = i % 5, y = i / 5; - g.drawRect(TILE_WIDTH * x, TILE_HEIGHT * y, - TILE_WIDTH, TILE_HEIGHT); + // paint our tiles + for (int i = 0; i < _tiles.length; i++) { + _tiles[i].paint((Graphics2D)g, 0, 0); } } @@ -121,12 +54,5 @@ public class TileGeometryTest frame.show(); } - protected Polygon[] _polys; - protected Color[] _colors; - - protected static Color[] COLOR_MAP = { - Color.red, // CITY - Color.green, // GRASS - Color.black // ROAD - }; + protected VenisonTile[] _tiles = new VenisonTile[TILE_TYPES]; } diff --git a/projects/atlanti/src/java/com/samskivert/atlanti/client/AtlantiBoard.java b/projects/atlanti/src/java/com/samskivert/atlanti/client/AtlantiBoard.java index 8dc4bf23..0f9b4245 100644 --- a/projects/atlanti/src/java/com/samskivert/atlanti/client/AtlantiBoard.java +++ b/projects/atlanti/src/java/com/samskivert/atlanti/client/AtlantiBoard.java @@ -1,11 +1,13 @@ // -// $Id: AtlantiBoard.java,v 1.6 2001/10/16 01:41:55 mdb Exp $ +// $Id: AtlantiBoard.java,v 1.7 2001/10/16 09:31:46 mdb Exp $ package com.threerings.venison; import java.awt.*; import java.awt.event.*; import javax.swing.*; + +import java.util.Arrays; import java.util.Iterator; import com.samskivert.swing.Controller; @@ -343,7 +345,12 @@ public class VenisonBoard TestDSet set = new TestDSet(); set.addTile(new VenisonTile(CITY_TWO, false, WEST, 0, 0)); - set.addTile(new VenisonTile(CITY_FOUR, false, NORTH, 0, 1)); + set.addTile(new VenisonTile(CITY_TWO, false, WEST, -1, 1)); + set.addTile(new VenisonTile(CITY_TWO, false, WEST, -1, -1)); + set.addTile(new VenisonTile(CURVED_ROAD, false, WEST, 0, 2)); + VenisonTile target = + new VenisonTile(DISCONNECTED_CITY_TWO, false, NORTH, 0, 1); + set.addTile(target); set.addTile(new VenisonTile(CITY_THREE, false, WEST, 1, 1)); set.addTile(new VenisonTile(CITY_THREE_ROAD, false, EAST, 1, 2)); set.addTile(new VenisonTile(CITY_THREE, false, NORTH, -1, 0)); @@ -353,6 +360,15 @@ public class VenisonBoard VenisonTile placing = new VenisonTile(CITY_TWO, false, NORTH, 0, 0); board.setTileToBePlaced(placing); + // set a feature group to test propagation + VenisonTile[] tiles = new VenisonTile[set.size()]; + Iterator iter = set.elements(); + for (int i = 0; iter.hasNext(); i++) { + tiles[i] = (VenisonTile)iter.next(); + } + Arrays.sort(tiles); + TileUtil.setFeatureGroup(tiles, target, 0, 1, 0); + frame.getContentPane().add(board, BorderLayout.CENTER); frame.pack(); SwingUtil.centerWindow(frame); diff --git a/projects/atlanti/src/java/com/samskivert/atlanti/data/AtlantiTile.java b/projects/atlanti/src/java/com/samskivert/atlanti/data/AtlantiTile.java index 9975b8cb..341484ad 100644 --- a/projects/atlanti/src/java/com/samskivert/atlanti/data/AtlantiTile.java +++ b/projects/atlanti/src/java/com/samskivert/atlanti/data/AtlantiTile.java @@ -1,5 +1,5 @@ // -// $Id: AtlantiTile.java,v 1.5 2001/10/16 01:41:55 mdb Exp $ +// $Id: AtlantiTile.java,v 1.6 2001/10/16 09:31:46 mdb Exp $ package com.threerings.venison; @@ -24,8 +24,12 @@ import com.threerings.presents.dobj.DSet; * Represents a single tile in play on the Venison game board. */ public class VenisonTile - implements DSet.Element, TileCodes, Cloneable + implements DSet.Element, TileCodes, Cloneable, Comparable { + /** The starting tile. */ + public static final VenisonTile STARTING_TILE = + new VenisonTile(CITY_ONE_ROAD_STRAIGHT, false, NORTH, 0, 0); + /** The tile type. */ public int type; @@ -38,6 +42,20 @@ public class VenisonTile /** The tile's x and y coordinates. */ public int x, y; + /** An array of claim group values that correspond to the features of + * this tile. If a piecen has claimed a feature on this tile or that + * connects to this tile, it will be represented here by a non-zero + * claim group in the array slot that corresponds to the claimed + * feature. */ + public int[] claims; + + /** A reference to our static feature descriptions. */ + public int[] features; + + /** A reference to the piecen on this tile or null if no piecen has + * been placed on this tile. */ + // public Piecen piecen; + /** * Constructs a tile with all of the supplied tile information. */ @@ -49,6 +67,12 @@ public class VenisonTile this.orientation = orientation; this.x = x; this.y = y; + + // grab a reference to our feature information + features = TileUtil.TILE_FEATURES[type-1]; + + // create our claims array + claims = new int[features.length/2]; } /** @@ -68,6 +92,62 @@ public class VenisonTile // nothing doing } + /** + * Looks for a feature in this tile that matches the supplied feature + * edge mask and returns the index of that feature in this tile's + * {@link #claims} array. + * + * @return the index of the matching feature or -1 if no feature + * matched. + */ + public int getFeatureIndex (int featureMask) + { + // translate the feature mask into our orientation + featureMask = TileUtil.translateMask(featureMask, -orientation); + + for (int i = 0; i < features.length; i += 2) { + int fmask = features[i+1]; + if ((fmask & featureMask) != 0) { + return i/2; + } + } + + // no match + return -1; + } + + /** + * Looks for a feature in this tile that matches the supplied feature + * edge mask and returns the claim group to which that feature belongs + * (which may be zero). + * + * @return the claim group to which the feature that matches the + * supplied mask belongs, or zero if no feature matched the supplied + * mask. + */ + public int getFeatureGroup (int featureMask) + { + int fidx = getFeatureIndex(featureMask); + return fidx < 0 ? 0 : claims[fidx]; + } + + /** + * Sets the claim group for the feature with the specified index. This + * also updates the claim group for any piecen that was placed on that + * feature as well. + * + * @param featureIndex the index of the feature to update. + * @param claimGroup the claim group to associate with the feature. + */ + public void setFeatureGroup (int featureIndex, int claimGroup) + { + Log.info("Setting feature group [tile=" + this + + ", fidx=" + featureIndex + ", cgroup=" + claimGroup + "]."); + claims[featureIndex] = claimGroup; + + // TBD: update the piecen + } + /** * Paints this tile to the specified graphics context at its assigned * location, accounting for the supplied x and y offsets of the @@ -97,8 +177,13 @@ public class VenisonTile // draw our shapes using the proper orientation GeneralPath[] paths = _shapes[tidx][orientation]; + IntTuple[] types = _types[tidx]; for (int i = 0; i < paths.length; i++) { - g.setColor(COLOR_MAP[_types[tidx][i].left]); + if (claims[types[i].right] != 0) { + g.setColor(CLAIMED_COLOR_MAP[types[i].left]); + } else { + g.setColor(COLOR_MAP[types[i].left]); + } g.fill(paths[i]); } @@ -118,6 +203,27 @@ public class VenisonTile return new VenisonTile(type, hasShield, orientation, x, y); } + /** + * Used to order tiles (which is done by board position). + */ + public int compareTo (Object other) + { + // we will either be compared to another tile or to a coordinate + // object + if (other instanceof VenisonTile) { + VenisonTile tile = (VenisonTile)other; + return (tile.x == x) ? y - tile.y : x - tile.x; + + } else if (other instanceof IntTuple) { + IntTuple coord = (IntTuple)other; + return (coord.left == x) ? y - coord.right : x - coord.left; + + } else { + // who knows... + return -1; + } + } + // documentation inherited public Object getKey () { @@ -153,14 +259,12 @@ public class VenisonTile */ protected void createShapes () { - System.out.println("Creating shapes " + this + "."); - int tidx = type-1; ArrayList polys = new ArrayList(); ArrayList types = new ArrayList(); // the first feature is the background color - Object[] features = (Object[])TileUtil.TILE_FEATURES[tidx]; + Object[] features = (Object[])TileUtil.TILE_FEATURE_GEOMS[tidx]; IntTuple base = (IntTuple)features[0]; // add a polygon containing the whole tile @@ -253,6 +357,15 @@ public class VenisonTile protected static Color[] COLOR_MAP = { Color.red, // CITY Color.green, // GRASS - Color.black // ROAD + Color.black, // ROAD + Color.yellow // CLOISTER + }; + + /** Maps feature types to colors for claimed features. */ + protected static Color[] CLAIMED_COLOR_MAP = { + Color.red.darker(), // CITY + Color.green.darker(), // GRASS + Color.black.brighter(), // ROAD + Color.yellow.darker(), // CLOISTER }; } diff --git a/projects/atlanti/src/java/com/samskivert/atlanti/data/TileCodes.java b/projects/atlanti/src/java/com/samskivert/atlanti/data/TileCodes.java index 74221308..906141ee 100644 --- a/projects/atlanti/src/java/com/samskivert/atlanti/data/TileCodes.java +++ b/projects/atlanti/src/java/com/samskivert/atlanti/data/TileCodes.java @@ -1,5 +1,5 @@ // -// $Id: TileCodes.java,v 1.3 2001/10/16 01:41:55 mdb Exp $ +// $Id: TileCodes.java,v 1.4 2001/10/16 09:31:46 mdb Exp $ package com.threerings.venison; @@ -58,7 +58,7 @@ public interface TileCodes public static final int CITY_ONE_ROAD_STRAIGHT = 13; /** A cloister tile. */ - public static final int CLOISTER = 14; + public static final int CLOISTER_PLAIN = 14; /** A cloister tile with a road extending from the cloister. */ public static final int CLOISTER_ROAD = 15; @@ -78,6 +78,7 @@ public interface TileCodes /** The number of different tile types. */ public static final int TILE_TYPES = 19; + /** A tile orientation constant indicating the tile is in its default * orientation. */ public static final int NORTH = 0; @@ -98,6 +99,7 @@ public interface TileCodes public static final String[] ORIENT_NAMES = new String[] { "N", "E", "S", "W" }; + /** The tile image width in pixels. */ public static int TILE_WIDTH = 90; @@ -113,4 +115,44 @@ public interface TileCodes /** A tile edge constant indicating a road edge. */ public static final int ROAD = 2; + + /** A constant indicating a cloister. */ + public static final int CLOISTER = 3; + + + /** Bit mask for a north connecting feature. */ + public static final int NORTH_F = 0x1 << 0; + + /** Bit mask for an east connecting feature. */ + public static final int EAST_F = 0x1 << 1; + + /** Bit mask for a south connecting feature. */ + public static final int SOUTH_F = 0x1 << 2; + + /** Bit mask for a west connecting feature. */ + public static final int WEST_F = 0x1 << 3; + + /** Bit mask for a north by northeast connecting feature. */ + public static final int NNE_F = 0x1 << 4; + + /** Bit mask for an east by northeast connecting feature. */ + public static final int ENE_F = 0x1 << 5; + + /** Bit mask for an east by southeast connecting feature. */ + public static final int ESE_F = 0x1 << 6; + + /** Bit mask for a south by southeast connecting feature. */ + public static final int SSE_F = 0x1 << 7; + + /** Bit mask for a south by southwest connecting feature. */ + public static final int SSW_F = 0x1 << 8; + + /** Bit mask for a west by southwest connecting feature. */ + public static final int WSW_F = 0x1 << 9; + + /** Bit mask for a west by northwest connecting feature. */ + public static final int WNW_F = 0x1 << 10; + + /** Bit mask for a north by northwest connecting feature. */ + public static final int NNW_F = 0x1 << 11; } diff --git a/projects/atlanti/src/java/com/samskivert/atlanti/server/AtlantiManager.java b/projects/atlanti/src/java/com/samskivert/atlanti/server/AtlantiManager.java index f35569de..9ef5d4dc 100644 --- a/projects/atlanti/src/java/com/samskivert/atlanti/server/AtlantiManager.java +++ b/projects/atlanti/src/java/com/samskivert/atlanti/server/AtlantiManager.java @@ -1,5 +1,5 @@ // -// $Id: AtlantiManager.java,v 1.6 2001/10/16 01:41:55 mdb Exp $ +// $Id: AtlantiManager.java,v 1.7 2001/10/16 09:31:46 mdb Exp $ package com.threerings.venison; @@ -57,7 +57,7 @@ public class VenisonManager // clear out the tile set _venobj.setTiles(new DSet(VenisonTile.class)); - _venobj.addToTiles(TileUtil.STARTING_TILE); + _venobj.addToTiles(VenisonTile.STARTING_TILE); } protected void turnWillStart () @@ -101,8 +101,16 @@ public class VenisonManager public void handleEvent (MessageEvent event) { VenisonTile tile = (VenisonTile)event.getArgs()[0]; - // don't do no checking at present - _venobj.addToTiles(tile); + + // make sure this is a valid placement + if (TileUtil.isValidPlacement(_venobj.tiles.elements(), tile)) { + // add the tile to the tiles set + _venobj.addToTiles(tile); + + } else { + Log.warning("Received invalid placement " + event + "."); + } + // end the turn endTurn(); } diff --git a/projects/atlanti/src/java/com/samskivert/atlanti/util/TileUtil.java b/projects/atlanti/src/java/com/samskivert/atlanti/util/TileUtil.java index fc4f2d76..69984d20 100644 --- a/projects/atlanti/src/java/com/samskivert/atlanti/util/TileUtil.java +++ b/projects/atlanti/src/java/com/samskivert/atlanti/util/TileUtil.java @@ -1,10 +1,11 @@ // -// $Id: TileUtil.java,v 1.4 2001/10/16 01:41:55 mdb Exp $ +// $Id: TileUtil.java,v 1.5 2001/10/16 09:31:46 mdb Exp $ package com.threerings.venison; import java.awt.Polygon; +import java.util.Arrays; import java.util.ArrayList; import java.util.List; import java.util.Iterator; @@ -16,10 +17,6 @@ import com.samskivert.util.IntTuple; */ public class TileUtil implements TileCodes { - /** The starting tile. */ - public static final VenisonTile STARTING_TILE = - new VenisonTile(CITY_ONE_ROAD_STRAIGHT, false, NORTH, 0, 0); - /** * Returns a list containing the standard tile set for the Venison * game. The list is a clone, so it can be bent, folded and modified @@ -165,6 +162,212 @@ public class TileUtil implements TileCodes return matchedAnEdge; } + /** + * When a tile is placed on the board, this method should be called on + * it to propagate existing claims to the appropriate features on this + * tile. It will determine if any city features are connected to + * cities that are already claimed, and if any road features are + * connected to roads that are already claimed and if any grassland is + * connected to grassland that is claimed. + * + *
If, in the process of initializing the claims for this tile, we + * discover that this tile connects two previously disconnected + * claims, those claims will be joined. The affected tiles and piecens + * will have their claim groups updated. + * + * @param tiles a sorted array of the tiles on the board (which need + * not include the tile whose features are being configured). + * @param tile the tile whose features should be configured. + */ + public static void initClaims (VenisonTile[] tiles, VenisonTile tile) + { + // obtain our neighboring tiles + VenisonTile[] neighbors = new VenisonTile[4]; + neighbors[NORTH] = findTile(tiles, tile.x, tile.y-1); + neighbors[EAST] = findTile(tiles, tile.x+1, tile.y); + neighbors[SOUTH] = findTile(tiles, tile.x, tile.y+1); + neighbors[WEST] = findTile(tiles, tile.x-1, tile.y); + + // for each feature in the tile, determine whether or not the + // neighboring tile's matching feature is claimed + for (int i = 0; i < tile.features.length; i += 2) { + int ftype = tile.features[i]; + int fmask = tile.features[i+1]; + int cgroup = 0; + + // iterate over all of the possible adjacency possibilities + for (int c = 0; c < ADJADCENCY_MAP.length; c += 3) { + int mask = ADJADCENCY_MAP[c]; + int dir = ADJADCENCY_MAP[c+1]; + int opp_mask = ADJADCENCY_MAP[c+2]; + + // if this feature doesn't have this edge, skip it + if ((fmask & mask) == 0) { + continue; + } + + // translate the target direction accordingly + dir = (dir + tile.orientation) % 4; + + // make sure we have a neighbor in the appropriate + // direction + if (neighbors[dir] == null) { + continue; + } + + // it looks like we have a match, so translate the target + // stuff into our orientation + mask = translateMask(mask, tile.orientation); + opp_mask = translateMask(opp_mask, tile.orientation); + + if (cgroup != 0) { + // if we've already been assigned to a group, we + // propagate our group to the opposing feature + int fidx = neighbors[dir].getFeatureIndex(opp_mask); + if (fidx >= 0) { + setFeatureGroup(tiles, neighbors[dir], + fidx, cgroup, mask); + } else { + Log.warning("Can't join-propagate feature " + + "[self=" + tile + + ", target=" + neighbors[dir] + + ", fidx=" + fidx + ", cgroup=" + cgroup + + ", destEdge=" + opp_mask + + ", srcEdge=" + mask + "]."); + } + + } else { + // otherwise, we inherit the group of the opposing + // feature + cgroup = neighbors[dir].getFeatureGroup(opp_mask); + } + } + + // finally initialize the feature's claim group + tile.claims[i/2] = cgroup; + } + } + + /** + * Sets the claim group for the specified feature in this tile and + * propagates that claim group to all connected features. + * + * @param tiles a sorted array of the tiles on the board. + * @param tile the tile that contains the feature whose claim group is + * being set. + * @param featureIndex the index of the feature. + * @param claimGroup the claim group value to set. + * @param entryEdgeMask the edge from which we are propagating this + * claim group (to avoid traversing back over that edge when + * propagating the group further). + */ + public static void setFeatureGroup ( + VenisonTile[] tiles, VenisonTile tile, int featureIndex, + int claimGroup, int entryEdgeMask) + { + // set the claim group for this feature on this tile + tile.setFeatureGroup(featureIndex, claimGroup); + + // now propagate this feature to connected features + int ftype = tile.features[featureIndex*2]; + int fmask = tile.features[featureIndex*2+1]; + + // iterate over all of the possible adjacency possibilities + for (int c = 0; c < ADJADCENCY_MAP.length; c += 3) { + int mask = ADJADCENCY_MAP[c]; + int dir = ADJADCENCY_MAP[c+1]; + int opp_mask = ADJADCENCY_MAP[c+2]; + VenisonTile neighbor = null; + + // if this feature doesn't have this edge, skip it + if ((fmask & mask) == 0) { + continue; + } + + // figure out if this would be the tile from which we + // propagated into our current tile and skip it if so + opp_mask = translateMask(opp_mask, tile.orientation); + if ((opp_mask & entryEdgeMask) != 0) { + continue; + } + + // make sure we have a neighbor in this direction + dir = (dir + tile.orientation) % 4; + switch (dir) { + case NORTH: neighbor = findTile(tiles, tile.x, tile.y-1); break; + case EAST: neighbor = findTile(tiles, tile.x+1, tile.y); break; + case SOUTH: neighbor = findTile(tiles, tile.x, tile.y+1); break; + case WEST: neighbor = findTile(tiles, tile.x-1, tile.y); break; + } + if (neighbor == null) { + continue; + } + + // it looks like we have a match, so translate the target mask + // into our orientation + mask = translateMask(mask, tile.orientation); + + // propagate, propagate, propagate + int fidx = neighbor.getFeatureIndex(opp_mask); + if (fidx >= 0) { + setFeatureGroup(tiles, neighbor, fidx, claimGroup, mask); + + } else { + Log.warning("Can't propagate feature [self=" + tile + + ", target=" + neighbor + ", fidx=" + fidx + + ", cgroup=" + claimGroup + ", srcEdge=" + mask + + ", destEdge=" + opp_mask + "]."); + } + } + } + + /** + * Translates the feature edge mask into the orientation specified. + * For a forward translation, provide a positive valued orientation + * constant. For a backward translation, provide a negative valued + * orientation constant. + * + * @return the translated feature mask. + */ + public static int translateMask (int featureMask, int orientation) + { + int[] map = FEATURE_ORIENT_MAP[0]; + if ((featureMask & (NNE_F|ESE_F|SSW_F|WNW_F)) != 0) { + map = FEATURE_ORIENT_MAP[1]; + } else if ((featureMask & (ENE_F|SSE_F|WSW_F|NNW_F)) != 0) { + map = FEATURE_ORIENT_MAP[2]; + } + return xlateMask(map, featureMask, orientation); + } + + /** {@link #translateMask} helper function. */ + protected static int xlateMask ( + int[] map, int featureMask, int orientation) + { + int index = 0; + for (int i = 0; i < map.length; i++) { + if (map[i] == featureMask) { + return map[(i + 4 + orientation) % 4]; + } + } + return featureMask; + } + + /** + * Locates and returns the tile with the specified coordinates. + * + * @param tiles a sorted tiles array. + * + * @return the tile with the requested coordinates or null if no tile + * exists at those coordinates. + */ + protected static VenisonTile findTile (VenisonTile[] tiles, int x, int y) + { + IntTuple coord = new IntTuple(x, y); + int tidx = Arrays.binarySearch(tiles, coord); + return (tidx >= 0) ? tiles[tidx] : null; + } + /** * Returns the edge type for specified edge of the specified tile * type. @@ -250,6 +453,304 @@ public class TileUtil implements TileCodes list.add(tile); } + /** Used to figure out which edges match up to which when comparing + * adjacent tiles. */ + protected static final int[] EDGE_MAP = new int[] { + -1, NORTH, -1, + WEST, -1, EAST, + -1, SOUTH, -1 + }; + + /** A mapping from feature edge masks to tile directions and + * corresponding feature edge masks. */ + protected static final int[] ADJADCENCY_MAP = new int[] { + NORTH_F, NORTH, SOUTH_F, + EAST_F, EAST, WEST_F, + SOUTH_F, SOUTH, NORTH_F, + WEST_F, WEST, EAST_F, + NNW_F, NORTH, SSW_F, + NNE_F, NORTH, SSE_F, + ENE_F, EAST, WNW_F, + ESE_F, EAST, WSW_F, + SSE_F, SOUTH, NNE_F, + SSW_F, SOUTH, NNW_F, + WSW_F, WEST, ESE_F, + WNW_F, WEST, ENE_F, + }; + + /** Mapping table used to rotate feature facements. */ + public static final int[][] FEATURE_ORIENT_MAP = new int[][] { + // orientations rotate through one of three four-cycles + { NORTH_F, EAST_F, SOUTH_F, WEST_F }, + { NNE_F, ESE_F, SSW_F, WNW_F }, + { ENE_F, SSE_F, WSW_F, NNW_F }, + }; + + /** A table indicating which tiles have which edges. */ + protected static final int[] TILE_EDGES = new int[] { + -1, -1, -1, -1, // null tile + CITY, CITY, CITY, CITY, // CITY_FOUR + CITY, CITY, GRASS, CITY, // CITY_THREE + CITY, CITY, ROAD, CITY, // CITY_THREE_ROAD + CITY, GRASS, GRASS, CITY, // CITY_TWO + CITY, ROAD, ROAD, CITY, // CITY_TWO_ROAD + GRASS, CITY, GRASS, CITY, // CITY_TWO_ACROSS + CITY, CITY, GRASS, GRASS, // DISCONNECTED_CITY_TWO + GRASS, CITY, GRASS, CITY, // DISCONNECTED_CITY_TWO_ACROSS + CITY, GRASS, GRASS, GRASS, // CITY_ONE + CITY, ROAD, ROAD, GRASS, // CITY_ONE_ROAD_RIGHT + CITY, GRASS, ROAD, ROAD, // CITY_ONE_ROAD_LEFT + CITY, ROAD, ROAD, ROAD, // CITY_ONE_ROAD_TEE + CITY, ROAD, GRASS, ROAD, // CITY_ONE_ROAD_STRAIGHT + GRASS, GRASS, GRASS, GRASS, // CLOISTER_PLAIN + GRASS, GRASS, ROAD, GRASS, // CLOISTER_ROAD + ROAD, ROAD, ROAD, ROAD, // FOUR_WAY_ROAD + GRASS, ROAD, ROAD, ROAD, // THREE_WAY_ROAD + ROAD, GRASS, ROAD, GRASS, // STRAIGHT_ROAD + GRASS, GRASS, ROAD, ROAD, // CURVED_ROAD + }; + + /** A table describing the features of each tile and their edge + * connectedness. */ + protected static final int[][] TILE_FEATURES = new int[][] { + // one must offset tile type by one when indexing into this array + + { CITY, NORTH_F|EAST_F|SOUTH_F|WEST_F }, // CITY_FOUR + + { CITY, NORTH_F|EAST_F|WEST_F, // CITY_THREE + GRASS, SOUTH_F }, + + { CITY, NORTH_F|EAST_F|WEST_F, // CITY_THREE_ROAD + GRASS, SSW_F, + GRASS, SSE_F, + ROAD, SOUTH_F }, + + { CITY, NORTH_F|WEST_F, // CITY_TWO + GRASS, EAST_F|SOUTH_F }, + + { CITY, NORTH_F|WEST_F, // CITY_TWO_ROAD + GRASS, ENE_F|SSW_F, + GRASS, ESE_F|SSE_F, + ROAD, EAST_F|SOUTH_F }, + + { CITY, WEST_F|EAST_F, // CITY_TWO_ACROSS + GRASS, NORTH_F, + GRASS, SOUTH_F }, + + { GRASS, WEST_F|SOUTH_F, // DISCONNECTED_CITY_TWO + CITY, NORTH_F, + CITY, EAST_F }, + + { GRASS, NORTH_F|SOUTH_F, // DISCONNECTED_CITY_TWO_ACROSS + CITY, WEST_F, + CITY, EAST_F }, + + { GRASS, EAST_F|SOUTH_F|WEST_F, // CITY_ONE + CITY, NORTH_F }, + + { GRASS, ENE_F|SSW_F|WEST_F, // CITY_ONE_ROAD_RIGHT + GRASS, ESE_F|SSE_F, + ROAD, EAST_F|SOUTH_F, + CITY, NORTH_F }, + + { GRASS, EAST_F|SSE_F, WNW_F, // CITY_ONE_ROAD_LEFT + GRASS, SSW_F|WSW_F, + ROAD, SOUTH_F|WEST_F, + CITY, NORTH_F }, + + { GRASS, ENE_F|WNW_F, // CITY_ONE_ROAD_TEE + GRASS, ESE_F|SSE_F, + GRASS, SSW_F|WSW_F, + ROAD, EAST_F, + ROAD, SOUTH_F, + ROAD, WEST_F, + CITY, NORTH_F }, + + { GRASS, ENE_F|WNW_F, // CITY_ONE_ROAD_STRAIGHT + GRASS, ESE_F, SOUTH_F, WSW_F, + ROAD, EAST_F|WEST_F, + CITY, NORTH_F }, + + { GRASS, NORTH_F|EAST_F|SOUTH_F|WEST_F, // CLOISTER_PLAIN + CLOISTER, 0}, + + { GRASS, NORTH_F|EAST_F|WEST_F, // CLOISTER_ROAD + CLOISTER, 0, + ROAD, SOUTH_F }, + + { GRASS, WNW_F|NNW_F, // FOUR_WAY_ROAD + GRASS, NNE_F|ENE_F, + GRASS, ESE_F|SSE_F, + GRASS, SSW_F|WSW_F, + ROAD, NORTH_F, + ROAD, EAST_F, + ROAD, SOUTH_F, + ROAD, WEST_F }, + + { GRASS, WNW_F|NORTH_F|ENE_F, // THREE_WAY_ROAD + GRASS, ESE_F|SSE_F, + GRASS, SSW_F|WSW_F, + ROAD, EAST_F, + ROAD, SOUTH_F, + ROAD, WEST_F }, + + { GRASS, NNE_F|EAST_F|SSE_F, // STRAIGHT_ROAD + GRASS, SSW_F|WEST_F|NNW_F, + ROAD, NORTH_F|SOUTH_F }, + + { GRASS, WNW_F|NORTH_F|EAST_F|SSE_F, // CURVED_ROAD + GRASS, SSW_F|WSW_F, + ROAD, SOUTH_F|WEST_F }, + }; + + /** A table describing the geometry of the features (cities, roads, + * etc.) of each tile. */ + protected static final Object[] TILE_FEATURE_GEOMS = new Object[] { + // one must offset tile type by one when indexing into this array + + new Object[] { new IntTuple(CITY, 0), }, // CITY_FOUR + + new Object[] { new IntTuple(CITY, 0), // CITY_THREE + new IntTuple(GRASS, 1), + new int[] { 0, 4, 1, 3, 3, 3, 4, 4 }}, + + new Object[] { new IntTuple(CITY, 0), // CITY_THREE_ROAD + new IntTuple(GRASS, 1), + new int[] { 0, 4, 1, 3, 2, 3, 2, 4 }, + new IntTuple(GRASS, 2), + new int[] { 2, 4, 2, 3, 3, 3, 4, 4 }, + new IntTuple(ROAD, 3), + new int[] { 2, 3, 2, 4 }}, + + new Object[] { new IntTuple(CITY, 0), // CITY_TWO + new IntTuple(GRASS, 1), + new int[] { 0, 4, 4, 0, 4, 4 }}, + + new Object[] { new IntTuple(CITY, 0), // CITY_TWO_ROAD + new IntTuple(GRASS, 1), + new int[] { 0, 4, 4, 0, 4, 2, 2, 4 }, + new IntTuple(GRASS, 2), + new int[] { 2, 4, 4, 2, 4, 4 }, + new IntTuple(ROAD, 3), + new int[] { 2, 4, 4, 2 }}, + + new Object[] { new IntTuple(CITY, 0), // CITY_TWO_ACROSS + new IntTuple(GRASS, 1), + new int[] { 0, 0, 1, 1, 3, 1, 4, 0 }, + new IntTuple(GRASS, 2), + new int[] { 0, 4, 1, 3, 3, 3, 4, 4 }}, + + new Object[] { new IntTuple(GRASS, 0), // DISCONNECTED_CITY_TWO + new IntTuple(CITY, 1), + new int[] { 0, 0, 1, 1, 3, 1, 4, 0 }, + new IntTuple(CITY, 2), + new int[] { 4, 0, 3, 1, 3, 3, 4, 4 }}, + + new Object[] { new IntTuple(GRASS, 0), // DISCONNECTED_CITY_TWO_ACROSS + new IntTuple(CITY, 1), + new int[] { 0, 0, 1, 1, 1, 3, 0, 4 }, + new IntTuple(CITY, 2), + new int[] { 4, 0, 3, 1, 3, 3, 4, 4 }}, + + new Object[] { new IntTuple(GRASS, 0), // CITY_ONE + new IntTuple(CITY, 1), + new int[] { 0, 0, 1, 1, 3, 1, 4, 0 }}, + + new Object[] { new IntTuple(GRASS, 0), // CITY_ONE_ROAD_RIGHT + new IntTuple(GRASS, 1), + new int[] { 2, 2, 4, 2, 4, 4, 2, 4 }, + new IntTuple(ROAD, 2), + new int[] { 2, 2, 2, 4 }, + new IntTuple(ROAD, 2), + new int[] { 2, 2, 4, 2 }, + new IntTuple(CITY, 3), + new int[] { 0, 0, 1, 1, 3, 1, 4, 0 }}, + + new Object[] { new IntTuple(GRASS, 0), // CITY_ONE_ROAD_LEFT + new IntTuple(GRASS, 1), + new int[] { 0, 2, 2, 2, 2, 4, 0, 4 }, + new IntTuple(ROAD, 2), + new int[] { 2, 2, 2, 4 }, + new IntTuple(ROAD, 2), + new int[] { 0, 2, 2, 2 }, + new IntTuple(CITY, 3), + new int[] { 0, 0, 1, 1, 3, 1, 4, 0 }}, + + new Object[] { new IntTuple(GRASS, 0), // CITY_ONE_ROAD_TEE + new IntTuple(GRASS, 1), + new int[] { 2, 2, 4, 2, 4, 4, 2, 4 }, + new IntTuple(GRASS, 2), + new int[] { 0, 2, 2, 2, 2, 4, 0, 4 }, + new IntTuple(ROAD, 3), + new int[] { 0, 2, 2, 2 }, + new IntTuple(ROAD, 4), + new int[] { 2, 2, 4, 2 }, + new IntTuple(ROAD, 5), + new int[] { 2, 2, 2, 4 }, + new IntTuple(CITY, 6), + new int[] { 0, 0, 1, 1, 3, 1, 4, 0 }}, + + new Object[] { new IntTuple(GRASS, 0), // CITY_ONE_ROAD_STRAIGHT + new IntTuple(GRASS, 1), + new int[] { 0, 2, 4, 2, 4, 4, 0, 4 }, + new IntTuple(ROAD, 2), + new int[] { 0, 2, 4, 2 }, + new IntTuple(CITY, 3), + new int[] { 0, 0, 1, 1, 3, 1, 4, 0 }}, + + new Object[] { new IntTuple(GRASS, 0), // CLOISTER_PLAIN + new IntTuple(CLOISTER, 1), + new int[] { 1, 1, 3, 1, 3, 3, 1, 3 }}, + + new Object[] { new IntTuple(GRASS, 0), // CLOISTER_ROAD + new IntTuple(CLOISTER, 1), + new int[] { 1, 1, 3, 1, 3, 3, 1, 3 }, + new IntTuple(ROAD, 2), + new int[] { 2, 3, 2, 4 }}, + + new Object[] { new IntTuple(GRASS, 0), // FOUR_WAY_ROAD + new IntTuple(GRASS, 1), + new int[] { 2, 0, 4, 0, 4, 2, 2, 2 }, + new IntTuple(GRASS, 2), + new int[] { 2, 2, 4, 2, 4, 4, 2, 4 }, + new IntTuple(GRASS, 3), + new int[] { 0, 2, 2, 2, 2, 4, 0, 4 }, + new IntTuple(ROAD, 4), + new int[] { 2, 0, 2, 2 }, + new IntTuple(ROAD, 5), + new int[] { 2, 2, 4, 2 }, + new IntTuple(ROAD, 6), + new int[] { 2, 2, 2, 4 }, + new IntTuple(ROAD, 7), + new int[] { 0, 2, 2, 2 }}, + + new Object[] { new IntTuple(GRASS, 0), // THREE_WAY_ROAD + new IntTuple(GRASS, 1), + new int[] { 2, 2, 4, 2, 4, 4, 2, 4 }, + new IntTuple(GRASS, 2), + new int[] { 0, 2, 2, 2, 2, 4, 0, 4 }, + new IntTuple(ROAD, 3), + new int[] { 2, 2, 4, 2 }, + new IntTuple(ROAD, 4), + new int[] { 2, 2, 2, 4 }, + new IntTuple(ROAD, 5), + new int[] { 0, 2, 2, 2 }}, + + new Object[] { new IntTuple(GRASS, 0), // STRAIGHT_ROAD + new IntTuple(GRASS, 1), + new int[] { 0, 2, 4, 2, 4, 4, 0, 4 }, + new IntTuple(ROAD, 2), + new int[] { 2, 0, 2, 4 }}, + + new Object[] { new IntTuple(GRASS, 0), // CURVED_ROAD + new IntTuple(GRASS, 1), + new int[] { 0, 2, 2, 2, 2, 4, 0, 4 }, + new IntTuple(ROAD, 2), + new int[] { 2, 2, 2, 4 }, + new IntTuple(ROAD, 2), + new int[] { 0, 2, 2, 2 }}, + }; + /** The standard tile set for a game of Venison. */ protected static ArrayList TILE_SET = new ArrayList(); @@ -279,7 +780,7 @@ public class TileUtil implements TileCodes addTiles(3, TILE_SET, new VenisonTile(CITY_ONE_ROAD_TEE, false)); addTiles(3, TILE_SET, new VenisonTile(CITY_ONE_ROAD_STRAIGHT, false)); - addTiles(4, TILE_SET, new VenisonTile(CLOISTER, false)); + addTiles(4, TILE_SET, new VenisonTile(CLOISTER_PLAIN, false)); addTiles(2, TILE_SET, new VenisonTile(CLOISTER_ROAD, false)); addTiles(1, TILE_SET, new VenisonTile(FOUR_WAY_ROAD, false)); @@ -287,160 +788,4 @@ public class TileUtil implements TileCodes addTiles(8, TILE_SET, new VenisonTile(STRAIGHT_ROAD, false)); addTiles(9, TILE_SET, new VenisonTile(CURVED_ROAD, false)); } - - /** Used to figure out which edges match up to which when comparing - * adjacent tiles. */ - protected static final int[] EDGE_MAP = new int[] { - -1, NORTH, -1, - WEST, -1, EAST, - -1, SOUTH, -1 - }; - - /** A table indicating which tiles have which edges. */ - protected static final int[] TILE_EDGES = new int[] { - -1, -1, -1, -1, // null tile - CITY, CITY, CITY, CITY, // CITY_FOUR - CITY, CITY, GRASS, CITY, // CITY_THREE - CITY, CITY, ROAD, CITY, // CITY_THREE_ROAD - CITY, GRASS, GRASS, CITY, // CITY_TWO - CITY, ROAD, ROAD, CITY, // CITY_TWO_ROAD - GRASS, CITY, GRASS, CITY, // CITY_TWO_ACROSS - CITY, CITY, GRASS, GRASS, // DISCONNECTED_CITY_TWO - GRASS, CITY, GRASS, CITY, // DISCONNECTED_CITY_TWO_ACROSS - CITY, GRASS, GRASS, GRASS, // CITY_ONE - CITY, ROAD, ROAD, GRASS, // CITY_ONE_ROAD_RIGHT - CITY, GRASS, ROAD, ROAD, // CITY_ONE_ROAD_LEFT - CITY, ROAD, ROAD, ROAD, // CITY_ONE_ROAD_TEE - CITY, ROAD, GRASS, ROAD, // CITY_ONE_ROAD_STRAIGHT - GRASS, GRASS, GRASS, GRASS, // CLOISTER - GRASS, GRASS, ROAD, GRASS, // CLOISTER_ROAD - ROAD, ROAD, ROAD, ROAD, // FOUR_WAY_ROAD - GRASS, ROAD, ROAD, ROAD, // THREE_WAY_ROAD - ROAD, GRASS, ROAD, GRASS, // STRAIGHT_ROAD - GRASS, GRASS, ROAD, ROAD, // CURVED_ROAD - }; - - /** A table describing the geometry of the features (cities, roads, - * etc.) of each tile. */ - protected static final Object[] TILE_FEATURES = new Object[] { - // one must offset tile type by one when indexing into this array - - new Object[] { new IntTuple(CITY, 0), }, // CITY_FOUR - - new Object[] { new IntTuple(CITY, 0), // CITY_THREE - new IntTuple(GRASS, 0), - new int[] { 0, 4, 1, 3, 3, 3, 4, 4 }}, - - new Object[] { new IntTuple(CITY, 0), // CITY_THREE_ROAD - new IntTuple(GRASS, 0), - new int[] { 0, 4, 1, 3, 2, 3, 2, 4 }, - new IntTuple(GRASS, 1), - new int[] { 2, 4, 2, 3, 3, 3, 4, 4 }, - new IntTuple(ROAD, 0), - new int[] { 2, 3, 2, 4 }}, - - new Object[] { new IntTuple(CITY, 0), // CITY_TWO - new IntTuple(GRASS, 0), - new int[] { 0, 4, 4, 0, 4, 4 }}, - - new Object[] { new IntTuple(CITY, 0), // CITY_TWO_ROAD - new IntTuple(GRASS, 0), - new int[] { 0, 4, 4, 0, 4, 2, 2, 4 }, - new IntTuple(GRASS, 0), - new int[] { 2, 4, 4, 2, 4, 4 }, - new IntTuple(ROAD, 0), - new int[] { 2, 4, 4, 2 }}, - - new Object[] { new IntTuple(CITY, 0), // CITY_TWO_ACROSS - new IntTuple(GRASS, 0), - new int[] { 0, 4, 1, 3, 3, 3, 4, 4 }, - new IntTuple(GRASS, 1), - new int[] { 0, 0, 1, 1, 3, 1, 4, 0 }}, - - new Object[] { new IntTuple(GRASS, 0), // DISCONNECTED_CITY_TWO - new IntTuple(CITY, 0), - new int[] { 0, 0, 1, 1, 3, 1, 4, 0 }, - new IntTuple(CITY, 1), - new int[] { 4, 0, 3, 1, 3, 3, 4, 4 }}, - - new Object[] { new IntTuple(GRASS, 0), // DISCONNECTED_CITY_TWO_ACROSS - new IntTuple(CITY, 0), - new int[] { 0, 0, 1, 1, 1, 3, 0, 4 }, - new IntTuple(CITY, 1), - new int[] { 4, 0, 3, 1, 3, 3, 4, 4 }}, - - new Object[] { new IntTuple(GRASS, 0), // CITY_ONE - new IntTuple(CITY, 0), - new int[] { 0, 0, 1, 1, 3, 1, 4, 0 }}, - - new Object[] { new IntTuple(GRASS, 0), // CITY_ONE_ROAD_RIGHT - new IntTuple(CITY, 0), - new int[] { 0, 0, 1, 1, 3, 1, 4, 0 }, - new IntTuple(ROAD, 0), - new int[] { 2, 2, 2, 4 }, - new IntTuple(ROAD, 0), - new int[] { 2, 2, 4, 2 }}, - - new Object[] { new IntTuple(GRASS, 0), // CITY_ONE_ROAD_LEFT - new IntTuple(CITY, 0), - new int[] { 0, 0, 1, 1, 3, 1, 4, 0 }, - new IntTuple(ROAD, 0), - new int[] { 2, 2, 2, 4 }, - new IntTuple(ROAD, 0), - new int[] { 0, 2, 2, 2 }}, - - new Object[] { new IntTuple(GRASS, 0), // CITY_ONE_ROAD_TEE - new IntTuple(CITY, 0), - new int[] { 0, 0, 1, 1, 3, 1, 4, 0 }, - new IntTuple(ROAD, 0), - new int[] { 0, 2, 2, 2 }, - new IntTuple(ROAD, 1), - new int[] { 2, 2, 4, 2 }, - new IntTuple(ROAD, 2), - new int[] { 2, 2, 2, 4 }}, - - new Object[] { new IntTuple(GRASS, 0), // CITY_ONE_ROAD_STRAIGHT - new IntTuple(CITY, 0), - new int[] { 0, 0, 1, 1, 3, 1, 4, 0 }, - new IntTuple(ROAD, 0), - new int[] { 0, 2, 4, 2 }}, - - new Object[] { new IntTuple(GRASS, 0), // CLOISTER - new IntTuple(CITY, 0), - new int[] { 1, 1, 3, 1, 3, 3, 1, 3 }}, - - new Object[] { new IntTuple(GRASS, 0), // CLOISTER_ROAD - new IntTuple(CITY, 0), - new int[] { 1, 1, 3, 1, 3, 3, 1, 3 }, - new IntTuple(ROAD, 0), - new int[] { 2, 3, 2, 4 }}, - - new Object[] { new IntTuple(GRASS, 0), // FOUR_WAY_ROAD - new IntTuple(ROAD, 0), - new int[] { 2, 0, 2, 2 }, - new IntTuple(ROAD, 1), - new int[] { 2, 2, 4, 2 }, - new IntTuple(ROAD, 2), - new int[] { 2, 2, 2, 4 }, - new IntTuple(ROAD, 3), - new int[] { 0, 2, 2, 2 }}, - - new Object[] { new IntTuple(GRASS, 0), // THREE_WAY_ROAD - new IntTuple(ROAD, 0), - new int[] { 0, 2, 2, 2 }, - new IntTuple(ROAD, 1), - new int[] { 2, 2, 4, 2 }, - new IntTuple(ROAD, 2), - new int[] { 2, 2, 2, 4 }}, - - new Object[] { new IntTuple(GRASS, 0), // STRAIGHT_ROAD - new IntTuple(ROAD, 0), - new int[] { 2, 0, 2, 4 }}, - - new Object[] { new IntTuple(GRASS, 0), // CURVED_ROAD - new IntTuple(ROAD, 0), - new int[] { 2, 2, 2, 4 }, - new IntTuple(ROAD, 0), - new int[] { 0, 2, 2, 2 }}, - }; }