package net.sf.colossus.variant; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import net.sf.colossus.common.Constants; import net.sf.colossus.common.Constants.HexsideGates; import net.sf.colossus.util.ArrayHelper; import net.sf.colossus.util.NullCheckPredicate; /** * The master board as part of a variant. * * Instances of this class are immutable. */ public class MasterBoard { private static final Logger LOGGER = Logger.getLogger(MasterBoard.class .getName()); /** * The number of hexes in the widest section. */ private final int horizSize; /** * The number of hexes in the tallest section. */ private final int vertSize; /** * "parity" of the board, so that hexes are displayed the proper way */ private final int boardParity; /** * TODO do something more OO, don't use arrays, fold {@link #show} into * it somehow (even using null seems better than the split). */ private final MasterHex[][] plainHexArray; /** * The hexes in the horizSize*vertSize array that actually exist are * represented by true. */ private final boolean[][] show; /** * A Set of all Tower hexes. */ private final Set<MasterHex> towerSet; /** * A cache for faster lookup of hexes using their labels. */ private final Map<String, MasterHex> hexByLabelCache = new HashMap<String, MasterHex>(); public MasterBoard(int horizSize, int vertSize, boolean show[][], MasterHex[][] plainHexArray) { this.horizSize = horizSize; this.vertSize = vertSize; this.show = show; this.plainHexArray = plainHexArray; initHexByLabelCache(); this.boardParity = computeBoardParity(); this.towerSet = new HashSet<MasterHex>(); setupTowerSet(); setupExits(plainHexArray); setupEntrances(plainHexArray); setupHexLabelSides(plainHexArray); setupNeighbors(plainHexArray); } public int getBoardParity() { return boardParity; } public MasterHex[][] getPlainHexArray() { return plainHexArray; } public boolean[][] getShow() { return show; } public int getHorizSize() { return horizSize; } public int getVertSize() { return vertSize; } private int computeBoardParity() { int parity = 0; outer: for (int x = 0; x < horizSize; x++) { for (int y = 0; y < vertSize - 1; y++) { if (show[x][y] && show[x][y + 1]) { parity = 1 - ((x + y) & 1); break outer; } } } return parity; } private void setupExits(MasterHex[][] h) { for (int i = 0; i < h.length; i++) { for (int j = 0; j < h[i].length; j++) { if (show[i][j]) { for (int k = 0; k < 3; k++) { if (h[i][j].getBaseExitType(k) != Constants.HexsideGates.NONE) { setupOneExit(h, i, j, k); } } } } } } private void setupOneExit(MasterHex[][] h, int i, int j, int k) { MasterHex dh = getHexByLabel(h[i][j].getBaseExitLabel(k)); assert dh != null : "null pointer ; i=" + i + ", j=" + j + ", k=" + k; if (dh.getXCoord() == i) { if (dh.getYCoord() == (j - 1)) { h[i][j].setExitType(0, h[i][j].getBaseExitType(k)); } else { assert dh.getYCoord() == (j + 1) : "bad exit ; i=" + i + ", j=" + j + ", k=" + k; h[i][j].setExitType(3, h[i][j].getBaseExitType(k)); } } else if (dh.getXCoord() == (i + 1)) { assert dh.getYCoord() == j : "bad exit ; i=" + i + ", j=" + j + ", k=" + k; h[i][j].setExitType(2 - ((i + j + boardParity) & 1), h[i][j].getBaseExitType(k)); } else { assert dh.getXCoord() == (i - 1) && dh.getYCoord() == j : "bad exit ; i=" + i + ", j=" + j + ", k=" + k; h[i][j].setExitType(4 + ((i + j + boardParity) & 1), h[i][j].getBaseExitType(k)); } } private void setupEntrances(MasterHex[][] h) { for (int i = 0; i < h.length; i++) { for (int j = 0; j < h[0].length; j++) { if (show[i][j]) { for (int k = 0; k < 6; k++) { HexsideGates gateType = h[i][j].getExitType(k); if (gateType != Constants.HexsideGates.NONE) { switch (k) { case 0: h[i][j - 1].setEntranceType(3, gateType); break; case 1: h[i + 1][j].setEntranceType(4, gateType); break; case 2: h[i + 1][j].setEntranceType(5, gateType); break; case 3: h[i][j + 1].setEntranceType(0, gateType); break; case 4: h[i - 1][j].setEntranceType(1, gateType); break; case 5: h[i - 1][j].setEntranceType(2, gateType); break; default: LOGGER.log(Level.SEVERE, "Bogus hexside"); } } } } } } } /** If the shortest hexside closest to the center of the board * is a short hexside, set the label side to it. * Else set the label side to the opposite hexside. */ private void setupHexLabelSides(MasterHex[][] h) { // First find the center of the board. int width = h.length; int height = h[0].length; // Subtract 1 to account for 1-based length of 0-based array. double midX = (width - 1) / 2.0; double midY = (height - 1) / 2.0; for (int i = 0; i < h.length; i++) { for (int j = 0; j < h[0].length; j++) { if (show[i][j]) { double deltaX = i - midX; // Adjust for aspect ratio of h array, which has roughly // twice as many horizontal as vertical elements even // though the board is roughly square. double deltaY = (j - midY) * width / height; double ratio; // Watch for division by zero. if (deltaY == 0) { ratio = deltaX * 99999999; } else { ratio = deltaX / deltaY; } // Derive the exact number if needed. if (Math.abs(ratio) < 0.6) { // Vertically dominated, so top or bottom hexside. // top, unless inverted if (isHexInverted(i, j)) { h[i][j].setLabelSide(3); } else { h[i][j].setLabelSide(0); } } else { // One of the left or right side hexsides. if (deltaX >= 0) { if (deltaY >= 0) { // 2 unless inverted if (isHexInverted(i, j)) { h[i][j].setLabelSide(5); } else { h[i][j].setLabelSide(2); } } else { // 4 unless inverted if (isHexInverted(i, j)) { h[i][j].setLabelSide(1); } else { h[i][j].setLabelSide(4); } } } else { if (deltaY >= 0) { // 4 unless inverted if (isHexInverted(i, j)) { h[i][j].setLabelSide(1); } else { h[i][j].setLabelSide(4); } } else { // 2 unless inverted if (isHexInverted(i, j)) { h[i][j].setLabelSide(5); } else { h[i][j].setLabelSide(2); } } } } } } } } public boolean isHexInverted(int i, int j) { return ((i + j) & 1) == boardParity; } private void setupNeighbors(MasterHex[][] h) { for (int i = 0; i < h.length; i++) { for (int j = 0; j < h[0].length; j++) { if (show[i][j]) { MasterHex hex = h[i][j]; if (hex.getExitType(0) != Constants.HexsideGates.NONE || hex.getEntranceType(0) != Constants.HexsideGates.NONE) { hex.setNeighbor(0, h[i][j - 1]); } if (hex.getExitType(1) != Constants.HexsideGates.NONE || hex.getEntranceType(1) != Constants.HexsideGates.NONE) { hex.setNeighbor(1, h[i + 1][j]); } if (hex.getExitType(2) != Constants.HexsideGates.NONE || hex.getEntranceType(2) != Constants.HexsideGates.NONE) { hex.setNeighbor(2, h[i + 1][j]); } if (hex.getExitType(3) != Constants.HexsideGates.NONE || hex.getEntranceType(3) != Constants.HexsideGates.NONE) { hex.setNeighbor(3, h[i][j + 1]); } if (hex.getExitType(4) != Constants.HexsideGates.NONE || hex.getEntranceType(4) != Constants.HexsideGates.NONE) { hex.setNeighbor(4, h[i - 1][j]); } if (hex.getExitType(5) != Constants.HexsideGates.NONE || hex.getEntranceType(5) != Constants.HexsideGates.NONE) { hex.setNeighbor(5, h[i - 1][j]); } } } } } private void initHexByLabelCache() { for (MasterHex[] row : plainHexArray) { for (MasterHex masterHex : row) { if (masterHex != null) { hexByLabelCache.put(masterHex.getLabel(), masterHex); } } } } /** * Retrieve a hex by its label. * * @param label The label to find the hex for. Valid label, not null. * @return The label found. */ public MasterHex getHexByLabel(final String label) { MasterHex hex = hexByLabelCache.get(label); // TODO such an assertion would be nice, but seems to fail when loading // a game: // assert hex != null : "No hex with label '" + label + "'"; return hex; } public Set<MasterHex> getTowerSet() { return Collections.unmodifiableSet(towerSet); } private void setupTowerSet() { ArrayHelper.findFirstMatch(this.plainHexArray, new NullCheckPredicate<MasterHex>(false) { @Override public boolean matchesNonNullValue(MasterHex hex) { if (hex.getTerrain().isTower()) { towerSet.add(hex); } return false; } }); } /** * Return a set of all hex labels. */ public Set<String> getAllHexLabels() { return hexByLabelCache.keySet(); } /** * Return a set of all hex labels. */ public Collection<MasterHex> getAllHexes() { return hexByLabelCache.values(); } }