package net.sf.colossus.variant; import java.awt.Color; import java.io.InputStream; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import net.sf.colossus.client.HexMap; import net.sf.colossus.game.EntrySide; import net.sf.colossus.server.VariantSupport; import net.sf.colossus.util.StaticResourceLoader; import net.sf.colossus.xmlparser.BattlelandLoader; /** * A master board terrain. * * This class describes a terrain on the master board, including its name, color and the * layout of a generic battle land. It can occur multiple times on a master board layout * attached to the {@link MasterHex} class. * * Battle land information could probably split out into another class, which could then * be immutable. */ public class MasterBoardTerrain implements Comparable<MasterBoardTerrain> { private static final Logger LOGGER = Logger .getLogger(MasterBoardTerrain.class.getName()); /** The (unique) identifier of this terrain. * Should also be used for all Battlelands purpose. */ private final String id; /** The name displayed on the Masterboard. * Should also be used for all recruiting purpose. * WARNING: this is not done that way yet. It shoud be, so that a single * name on the Masterboard will represent a single recruiting branch, * even if it' backed by several different Battlelands. This would also * remove a lot of duplicated entries in the Full Recruit Tree. * WIP. * ADDITIONAL WARNING: What about variant such as Balrog? The recruitment * is Hex-specific, not Terrain-specific... */ private final String displayName; /** Subtitle, for the Battlelands. Cosmetic only, but nice */ private String subtitle; private final Color color; /** TODO this should be a List<BattleHex> ... or a List<GUIBattleHex> ??? * If non-null, this is the list of hexes a defending legion will start * in, in a similar way to the Tower in the Default variant. */ private List<String> startList; /** Whether this is a Tower-like building, with regards to starting the * game, not recruiting or defender entering in a non-default location on * the Battlemap. */ private boolean isTower; private Map<HazardTerrain, Integer> hazardNumberMap; // TODO this should be a Map<HazardHexside, Integer> private Map<Character, Integer> hazardSideNumberMap; // TODO right now we set up both, until all queries can use the new form private Map<HazardHexside, Integer> hexsideHazardNumberMap; /** The other MasterBoardTerrain using the same recruit tree */ private final Set<MasterBoardTerrain> aliases = new TreeSet<MasterBoardTerrain>(); /** Whether this terrain uses another Terrain recruit tree. */ private final boolean isAlias; // TODO it might be worthwhile moving the battle land into a separate class private final BattleHex[][] battleHexes = new BattleHex[6][6]; private final BattleHex[] entrances = new BattleHex[6]; /** The recruiting tree of this terrain */ IRecruiting recruitingSubTree; public MasterBoardTerrain(String id, String displayName, Color color, boolean isAlias) { this.id = id; this.displayName = displayName; this.color = color; this.subtitle = null; this.isAlias = isAlias; setupHexArrays(); } private void setupHexArrays() { // Initialize game state hex array. for (int i = 0; i < battleHexes.length; i++) { for (int j = 0; j < battleHexes[0].length; j++) { if (HexMap.VISIBLE_HEXES[i][j]) { BattleHex hex = new BattleHex(i, j); battleHexes[i][j] = hex; } } } setupHexesGameState(); setupNeighbors(); setupEntrances(); } private void setupEntrances() { for (int k = 0; k < 6; k++) { entrances[k] = new BattleHex(-1, k); } entrances[0].setNeighbor(3, battleHexes[3][0]); entrances[0].setNeighbor(4, battleHexes[4][1]); entrances[0].setNeighbor(5, battleHexes[5][1]); entrances[1].setNeighbor(3, battleHexes[5][1]); entrances[1].setNeighbor(4, battleHexes[5][2]); entrances[1].setNeighbor(5, battleHexes[5][3]); entrances[1].setNeighbor(0, battleHexes[5][4]); entrances[2].setNeighbor(4, battleHexes[5][4]); entrances[2].setNeighbor(5, battleHexes[4][5]); entrances[2].setNeighbor(0, battleHexes[3][5]); entrances[3].setNeighbor(5, battleHexes[3][5]); entrances[3].setNeighbor(0, battleHexes[2][5]); entrances[3].setNeighbor(1, battleHexes[1][4]); entrances[3].setNeighbor(2, battleHexes[0][4]); entrances[4].setNeighbor(0, battleHexes[0][4]); entrances[4].setNeighbor(1, battleHexes[0][3]); entrances[4].setNeighbor(2, battleHexes[0][2]); entrances[5].setNeighbor(1, battleHexes[0][2]); entrances[5].setNeighbor(2, battleHexes[1][1]); entrances[5].setNeighbor(3, battleHexes[2][1]); entrances[5].setNeighbor(4, battleHexes[3][0]); } /** Add references to neighbor hexes. */ private void setupNeighbors() { for (int i = 0; i < battleHexes.length; i++) { for (int j = 0; j < battleHexes[0].length; j++) { if (HexMap.VISIBLE_HEXES[i][j]) { if (j > 0 && HexMap.VISIBLE_HEXES[i][j - 1]) { battleHexes[i][j] .setNeighbor(0, battleHexes[i][j - 1]); } if (i < 5 && HexMap.VISIBLE_HEXES[i + 1][j - ((i + 1) & 1)]) { battleHexes[i][j].setNeighbor(1, battleHexes[i + 1][j - ((i + 1) & 1)]); } if (i < 5 && j + (i & 1) < 6 && HexMap.VISIBLE_HEXES[i + 1][j + (i & 1)]) { battleHexes[i][j].setNeighbor(2, battleHexes[i + 1][j + (i & 1)]); } if (j < 5 && HexMap.VISIBLE_HEXES[i][j + 1]) { battleHexes[i][j] .setNeighbor(3, battleHexes[i][j + 1]); } if (i > 0 && j + (i & 1) < 6 && HexMap.VISIBLE_HEXES[i - 1][j + (i & 1)]) { battleHexes[i][j].setNeighbor(4, battleHexes[i - 1][j + (i & 1)]); } if (i > 0 && HexMap.VISIBLE_HEXES[i - 1][j - ((i + 1) & 1)]) { battleHexes[i][j].setNeighbor(5, battleHexes[i - 1][j - ((i + 1) & 1)]); } } } } } public BattleHex getEntrance(EntrySide entrySide) { return getHexByLabel("X" + entrySide.ordinal()); } /** Add terrain, hexsides, elevation, and exits to hexes. * Cliffs are bidirectional; other hexside obstacles are noted * only on the high side, since they only interfere with * uphill movement. */ private void setupHexesGameState() { List<String> directories = VariantSupport .getBattlelandsDirectoriesList(); BattleHex[][] hexModel = new BattleHex[battleHexes.length][battleHexes[0].length]; for (int i = 0; i < battleHexes.length; i++) { for (int j = 0; j < battleHexes[0].length; j++) { if (HexMap.VISIBLE_HEXES[i][j]) { hexModel[i][j] = new BattleHex(i, j); } } } try { // TODO variant loading code does not belong here { // static Battlelands InputStream batIS = StaticResourceLoader.getInputStream( getId() + ".xml", directories); BattlelandLoader bl = new BattlelandLoader(batIS, hexModel); List<String> tempTowerStartList = bl.getStartList(); setStartList(tempTowerStartList); setTower(bl.isTower()); setSubtitle(bl.getSubtitle()); } /* slow & inefficient... */ Map<HazardTerrain, Integer> t2n = new HashMap<HazardTerrain, Integer>(); for (HazardTerrain hTerrain : HazardTerrain.getAllHazardTerrains()) { int count = 0; for (int x = 0; x < 6; x++) { for (int y = 0; y < 6; y++) { if (HexMap.VISIBLE_HEXES[x][y]) { if (hexModel[x][y].getTerrain().equals(hTerrain)) { count++; } } } } t2n.put(hTerrain, Integer.valueOf(count)); } setHazardNumberMap(t2n); Collection<HazardHexside> hazardTypes = HazardHexside .getAllHazardHexsides(); // old way Map<Character, Integer> s2n = new HashMap<Character, Integer>(); // new way Map<HazardHexside, Integer> h2n = new HashMap<HazardHexside, Integer>(); for (HazardHexside hazard : hazardTypes) { int count = 0; for (int x = 0; x < 6; x++) { for (int y = 0; y < 6; y++) { if (HexMap.VISIBLE_HEXES[x][y]) { for (int k = 0; k < 6; k++) { if (hexModel[x][y].getHexsideHazard(k) == hazard) { count++; } } } } } char side = hazard.getCode(); // old way s2n.put(Character.valueOf(side), Integer.valueOf(count)); // new way h2n.put(hazard, Integer.valueOf(count)); } setHazardSideNumberMap(s2n); setHexsideHazardNumberMap(h2n); // map model into GUI for (int i = 0; i < hexModel.length; i++) { BattleHex[] row = hexModel[i]; for (int j = 0; j < row.length; j++) { BattleHex hex = row[j]; if (HexMap.VISIBLE_HEXES[i][j]) { battleHexes[i][j] = hex; } } } } catch (Exception e) { LOGGER.log(Level.SEVERE, "Battleland " + this.displayName + " loading failed.", e); e.printStackTrace(); } } /** * Look for the Hex matching the Label in this terrain. */ public BattleHex getHexByLabel(String label) { assert label != null : "We must have a label"; int x = 0; int y = Integer.parseInt(label.substring(1)); switch (label.charAt(0)) { case 'A': case 'a': x = 0; break; case 'B': case 'b': x = 1; break; case 'C': case 'c': x = 2; break; case 'D': case 'd': x = 3; break; case 'E': case 'e': x = 4; break; case 'F': case 'f': x = 5; break; case 'X': case 'x': /* entrances */ return entrances[y]; default: String message = "Label " + label + " is invalid"; LOGGER.log(Level.SEVERE, message); assert false : message; } y = 6 - y - Math.abs((x - 3) / 2); return battleHexes[x][y]; } public void setRecruitingSubTree(IRecruiting rst) { this.recruitingSubTree = rst; } public IRecruiting getRecruitingSubTree() { return recruitingSubTree; } public MasterBoardTerrain(String id, String displayName, Color color) { this(id, displayName, color, false); } public int compareTo(MasterBoardTerrain m) { return this.id.compareTo(m.id); } public void addAlias(MasterBoardTerrain t) { aliases.add(t); } public boolean isAlias() { return isAlias; } public Set<MasterBoardTerrain> getAliases() { return Collections.unmodifiableSet(aliases); } public String getId() { return id; } public String getDisplayName() { return displayName; } public String getSubtitle() { return subtitle; } public void setSubtitle(String s) { subtitle = s; } public Color getColor() { return color; } // TODO get rid of dependencies into client package public boolean hasNativeCombatBonus(CreatureType creature) { int bonusHazardCount = 0; int bonusHazardSideCount = 0; for (HazardTerrain hTerrain : HazardTerrain.getAllHazardTerrains()) { int count = this.getHazardCount(hTerrain); if (hTerrain.isNativeBonusTerrain() && creature.isNativeIn(hTerrain)) { bonusHazardCount += count; } else { if (hTerrain.isNonNativePenaltyTerrain() && !creature.isNativeIn(hTerrain)) { bonusHazardCount -= count; } } } final Collection<HazardHexside> hazardTypes = HazardHexside .getAllHazardHexsides(); for (HazardHexside hazard : hazardTypes) { int count = this.getHazardHexsideCount(hazard); if (hazard.isNativeBonusHexside() && (creature).isNativeAt(hazard)) { bonusHazardSideCount += count; } else { if (hazard.isNonNativePenaltyHexside() && !(creature).isNativeAt(hazard)) { bonusHazardSideCount -= count; } } } if (((bonusHazardCount + bonusHazardSideCount) > 0) && ((bonusHazardCount >= 3) || (bonusHazardSideCount >= 5))) { return true; } return false; } public void setStartList(List<String> startList) { this.startList = startList; } public List<String> getStartList() { if (startList == null) { return null; } return Collections.unmodifiableList(startList); } public void setTower(boolean isTower) { this.isTower = isTower; } public boolean isTower() { return isTower; } public boolean hasStartList() { return startList != null; } public void setHazardNumberMap(Map<HazardTerrain, Integer> hazardNumberMap) { this.hazardNumberMap = hazardNumberMap; } public int getHazardCount(HazardTerrain terrain) { return hazardNumberMap.get(terrain).intValue(); } public void setHazardSideNumberMap( Map<Character, Integer> hazardSideNumberMap) { this.hazardSideNumberMap = hazardSideNumberMap; } public int getHazardSideCount(char hazardSide) { return hazardSideNumberMap.get(Character.valueOf(hazardSide)) .intValue(); } public void setHexsideHazardNumberMap( Map<HazardHexside, Integer> hexsideHazardNumberMap) { this.hexsideHazardNumberMap = hexsideHazardNumberMap; } // TODO Keep the old style as paranoid counterCheck and compare results. // If this does now show up problems, the old way can be eliminated // (refactored 2009-04-06 by Clemens) public int getHazardHexsideCount(HazardHexside hazard) { int oldCount = getHazardSideCount(hazard.getCode()); int newCount = hexsideHazardNumberMap.get(hazard).intValue(); if (oldCount != newCount) { LOGGER.warning("Refactored getCount for hexside hazard types " + "returns different value (" + newCount + ") than old " + "one does (" + oldCount + ")"); } return newCount; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ? 0 : id.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MasterBoardTerrain other = (MasterBoardTerrain)obj; if (id == null) { if (other.id != null) return false; } return id.equals(other.id); } }