package net.sf.colossus.game; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import net.sf.colossus.client.LegionClientSide; import net.sf.colossus.variant.CreatureType; import net.sf.colossus.variant.ICustomRecruitBase; import net.sf.colossus.variant.IVariantKnower; import net.sf.colossus.variant.MasterBoardTerrain; import net.sf.colossus.variant.MasterHex; import net.sf.colossus.variant.Variant; import net.sf.colossus.xmlparser.TerrainRecruitLoader; /** * Implementation of a graph dedicated to the Recruit "Tree" (it's a directed * graph, not a tree, as we can have cycle in theory). * * Moved into game package. Does it belong more to game or variant package? * * TODO this is still string-based, see comment in {@link TerrainRecruitLoader} * * @author Romain Dolbeau */ public class RecruitGraph { private static final Logger LOGGER = Logger.getLogger(RecruitGraph.class .getName()); private Caretaker caretaker; private final IVariantKnower variantKnower; private final List<RecruitVertex> allVertex = new ArrayList<RecruitVertex>(); private final List<RecruitEdge> allEdge = new ArrayList<RecruitEdge>(); private final Map<String, RecruitVertex> creatureToVertex = new HashMap<String, RecruitVertex>(); /** 99 creatures can muster one means: can not muster at all */ public static final int BIGNUM = 99; /** * The vertex of the Recruit Graph * * @author Romain Dolbeau */ private static class RecruitVertex { private final String cre; private final RecruitGraph graph; private final List<RecruitEdge> outgoingEdges = new ArrayList<RecruitEdge>(); private final List<RecruitEdge> incomingEdges = new ArrayList<RecruitEdge>(); RecruitVertex(String name, RecruitGraph graph) { this.cre = name; this.graph = graph; } List<RecruitEdge> getOutgoingEdges() { List<RecruitEdge> oe = new ArrayList<RecruitEdge>(); oe.addAll(outgoingEdges); return oe; } List<RecruitEdge> getIncomingEdges() { List<RecruitEdge> ie = new ArrayList<RecruitEdge>(); ie.addAll(incomingEdges); return ie; } void addOutgoingEdge(RecruitEdge e) { if (!(outgoingEdges.contains(e))) { outgoingEdges.add(e); } } void addIncomingEdge(RecruitEdge e) { if (!(incomingEdges.contains(e))) { incomingEdges.add(e); } } String getCreatureName() { return cre; } int getRemaining() { if (graph.getCaretaker() != null) { CreatureType type = graph.getVariant().getCreatureByName(cre); return graph.getCaretaker().getAvailableCount(type); } else { return BIGNUM; } } @Override // TODO override hashCode() or leave both public boolean equals(Object obj) { if (this.getClass() != obj.getClass()) { return false; } RecruitVertex o2 = (RecruitVertex)obj; if (o2.getCreatureName().equals(cre)) { return true; } return false; } @Override public String toString() { return "RecruitVertex " + cre + " with " + outgoingEdges.size() + "exits"; } } /** * The edge of the Recruit Graph * * @author Romain Dolbeau */ private static class RecruitEdge { private final RecruitVertex src; private final RecruitVertex dst; private final int number; private final MasterBoardTerrain terrain; RecruitEdge(RecruitVertex src, RecruitVertex dst, int number, MasterBoardTerrain terrain) { this.src = src; this.dst = dst; this.number = number; this.terrain = terrain; } RecruitVertex getSource() { return src; } RecruitVertex getDestination() { return dst; } int getNumber() { return number; } MasterBoardTerrain getTerrain() { return terrain; } // TODO override hashCode(), too -- or leave both @Override public boolean equals(Object obj) { if (this.getClass() != obj.getClass()) { return false; } RecruitEdge o2 = (RecruitEdge)obj; return ((o2.getSource() == src) && (o2.getDestination() == dst) && (o2.getNumber() == number) && (o2.getTerrain() .equals(terrain))); } @Override public String toString() { return "RecruitEdge from " + number + " " + src.getCreatureName() + " to " + dst.getCreatureName() + " in " + terrain; } } /** * Models a recruit option for a given creature. * * This is an return object for the question which recruit options a particular * creature has. Each option consists of a terrain to muster in, a target creatures * and a number of start creatures required to upgrade. */ public static final class RecruitOption { private final MasterBoardTerrain terrain; private final String startCreature; private final String targetCreature; private final int numberRequired; public RecruitOption(MasterBoardTerrain terrain, String startCreature, String targetCreature, int numberRequired) { super(); this.terrain = terrain; this.startCreature = startCreature; this.targetCreature = targetCreature; this.numberRequired = numberRequired; } public MasterBoardTerrain getTerrain() { return terrain; } public String getStartCreature() { return startCreature; } public String getTargetCreature() { return targetCreature; } public int getNumberRequired() { return numberRequired; } } // TODO variantKnower is only a temporary solution. Instead the variant // should be passed in (not possible right now), or perhaps the Game, // from which the variant can be asked. public RecruitGraph(IVariantKnower variantKnower) { this.variantKnower = variantKnower; this.caretaker = null; } private RecruitVertex addVertex(String cre) { RecruitVertex temp = creatureToVertex.get(cre); if (temp == null) { temp = new RecruitVertex(cre, this); allVertex.add(temp); creatureToVertex.put(cre, temp); } return temp; } private RecruitVertex getVertex(String cre) { RecruitVertex temp = creatureToVertex.get(cre); if (temp == null) { LOGGER.log(Level.FINEST, "CUSTOM: Adding non-existant creature: " + cre + " to the graph."); temp = addVertex(cre); } return temp; } private RecruitEdge addEdge(RecruitVertex src, RecruitVertex dst, int number, MasterBoardTerrain terrain) { RecruitEdge e = new RecruitEdge(src, dst, number, terrain); allEdge.add(e); src.addOutgoingEdge(e); dst.addIncomingEdge(e); return e; } /** * Traverse the graph (depth first), assuming that all vertex in visited * have been already visited, and using the given legion for availability * of creatures (along with the caretakerInfo). This will ignore any * strange stuff such as Anything, AnyNonLord, and so on. OTOH It will * not ignore the Titan. * @param s The base vertex * @param visited Already visited vertexes * @param legion The legion to use for availability * @return The list of all reachable Vertex from parameter s. */ private List<RecruitVertex> traverse(RecruitVertex s, Set<RecruitVertex> visited, Legion legion) { List<RecruitVertex> all = new ArrayList<RecruitVertex>(); if (s != null) { all.add(s); visited.add(s); List<RecruitEdge> oe = s.getOutgoingEdges(); Iterator<RecruitEdge> it = oe.iterator(); while (it.hasNext()) { RecruitEdge e = it.next(); RecruitVertex v = e.getDestination(); String creName = s.getCreatureName(); int already = (legion == null ? 0 : ((LegionClientSide)legion) .numCreature(creName)); /* only explore if (1) not already visited (2) enough in current legion + caretaker to traverse (3) at least one of the destination still available */ if (!(visited.contains(v))) { if (((s.getRemaining() + already) >= e.getNumber()) && (v.getRemaining() > 0)) { all.addAll(traverse(v, visited, legion)); } else { LOGGER.log(Level.FINEST, "GRAPH: ignoring " + e + " as not enough creatures are left (a: " + already + " s: " + s.getRemaining() + " d: " + v.getRemaining() + ")"); } } } } return all; } Caretaker getCaretaker() { return caretaker; } private Variant getVariant() { return variantKnower.getTheCurrentVariant(); } /** * Give the List of RecruitEdge where the given creature is the source. * @param cre Name of the recruiting creature * @return A List of all the outgoing RecruitEdge. */ private List<RecruitEdge> getOutgoingEdges(String cre) { RecruitVertex temp = getVertex(cre); return temp.getOutgoingEdges(); } /** * Give the List of RecruitEdge where the given creature is the destination. * @param cre Name of the recruited creature * @return A List of all the incoming RecruitEdge. */ private List<RecruitEdge> getIncomingEdges(String cre) { RecruitVertex temp = getVertex(cre); return temp.getIncomingEdges(); } /** * Give the List of RecruitVertex still reachable through the given * creature from the given Legion. * @param cre Name of the base creature * @return A List of all the reachable RecruitVertex. */ private List<RecruitVertex> traverse(String cre, Legion legion) { return traverse(getVertex(cre), new HashSet<RecruitVertex>(), legion); } /* PUBLIC */ /** * Add an edge is the graph from a Creature to another, in a given number, * in a given terrain. * @param src Name of the recruiting creature * @param dst Name of the recruited creature * @param number Number of recruiters * @param terrain Terrain where the recruiting occurs */ public void addEdge(String src, String dst, int number, MasterBoardTerrain terrain) { addEdge(addVertex(src), addVertex(dst), number, terrain); } public int numberOfRecruiterNeeded(String recruiter, String recruit, MasterBoardTerrain terrain, MasterHex hex) { List<RecruitEdge> allEdge = getIncomingEdges(recruit); RecruitVertex source = getVertex(recruiter); CreatureType recruiterCre = getVariant().getCreatureByName(recruiter); CreatureType recruitCre = getVariant().getCreatureByName(recruit); // if the recruiter is a special such as Anything, avoid // crashing with NullPointerException boolean isLord = (recruiterCre == null ? false : recruiterCre.isLord()); boolean isDemiLord = (recruiterCre == null ? false : recruiterCre .isDemiLord()); int minValue = BIGNUM; Iterator<RecruitEdge> it = allEdge.iterator(); while (it.hasNext()) { RecruitEdge theEdge = it.next(); if (theEdge.getTerrain().equals(terrain)) { RecruitVertex tempSrc = theEdge.getSource(); if ((source == tempSrc) || (tempSrc.getCreatureName() .equals(TerrainRecruitLoader.Keyword_Anything)) || ((!isLord) && (!isDemiLord) && (tempSrc .getCreatureName() .equals(TerrainRecruitLoader.Keyword_AnyNonLord))) || ((isLord) && (tempSrc.getCreatureName() .equals(TerrainRecruitLoader.Keyword_Lord))) || ((isDemiLord) && (tempSrc.getCreatureName() .equals(TerrainRecruitLoader.Keyword_DemiLord)))) { if (minValue > theEdge.getNumber()) { minValue = theEdge.getNumber(); } } if (tempSrc.getCreatureName().startsWith( TerrainRecruitLoader.Keyword_Special)) { ICustomRecruitBase cri = TerrainRecruitLoader .getCustomRecruitBase(tempSrc.getCreatureName()); int v = cri.numberOfRecruiterNeeded(recruiterCre, recruitCre, hex); if (v < minValue) { minValue = v; } } } } return minValue; } /** * Set the Caretaker to use for availability of creatures. * @param caretaker The caretaker to use subsequently. */ public void setCaretaker(Caretaker caretaker) { this.caretaker = caretaker; } /** * Clear the graph of all Vertex & Edge. */ public void clear() { caretaker = null; allVertex.clear(); allEdge.clear(); creatureToVertex.clear(); } /** * What is the maximum "useful" number of a given creature for * recruitment purpose (excluding "Any" or "AnyNonLord"). * return value of -1 or 0 means the Creature cannot recruit except itself. * @param cre Name of the creature considered. * @return The higher number of creatures needed to recruit something. */ public int getMaximumUsefulNumber(String cre) { int mun = -1; List<RecruitEdge> outgoing = getOutgoingEdges(cre); Iterator<RecruitEdge> it = outgoing.iterator(); while (it.hasNext()) { RecruitEdge e = it.next(); if (e.getNumber() > mun) { mun = e.getNumber(); } } return mun; } /** * Return all the terrains (as String in a List) where the given * number of creature of the given name can recruit. * @param cre Name of the recruiting creature. * @param number Number of creature * @return A List of all Terrains where recruitment * is possible. */ public List<MasterBoardTerrain> getAllTerrainsWhereThisNumberOfCreatureRecruit( String cre, int number) { List<MasterBoardTerrain> at = new ArrayList<MasterBoardTerrain>(); List<RecruitEdge> outgoing = getOutgoingEdges(cre); Iterator<RecruitEdge> it = outgoing.iterator(); while (it.hasNext()) { RecruitEdge e = it.next(); if (e.getNumber() == number) { at.add(e.getTerrain()); } } return at; } /** * A list of what a creature can recruit. */ public List<RecruitOption> getAllThatThisCreatureCanRecruit(String cre) { List<RecruitOption> result = new ArrayList<RecruitOption>(); List<RecruitEdge> outgoing = getOutgoingEdges(cre); Iterator<RecruitEdge> it = outgoing.iterator(); while (it.hasNext()) { RecruitEdge e = it.next(); result.add(new RecruitOption(e.getTerrain(), cre, e .getDestination().getCreatureName(), e.getNumber())); } return result; } /** * A list of what can recruit a creature. */ public List<RecruitOption> getAllThatCanRecruitThisCreature(String cre) { List<RecruitOption> result = new ArrayList<RecruitOption>(); List<RecruitEdge> outgoing = getIncomingEdges(cre); Iterator<RecruitEdge> it = outgoing.iterator(); while (it.hasNext()) { RecruitEdge e = it.next(); result.add(new RecruitOption(e.getTerrain(), e.getSource() .getCreatureName(), cre, e.getNumber())); } return result; } /** * Return the name of the recruit for the given number of the given * recruiter in the given terrain, or null if there's none. * @param cre The recruiting creature. * @param number Number of creature * @param t Terrain in which the recruiting may occur. * @return The recruit. */ public CreatureType getRecruitFromRecruiterTerrainNumber(CreatureType cre, MasterBoardTerrain t, int number) { List<RecruitEdge> outgoing = getOutgoingEdges(cre.getName()); CreatureType v2 = null; Iterator<RecruitEdge> it = outgoing.iterator(); while (it.hasNext()) { RecruitEdge e = it.next(); if ((e.getNumber() == number) && (e.getTerrain().equals(t))) { v2 = getVariant().getCreatureByName( e.getDestination().getCreatureName()); } } return v2; } /** * Return the name of the best possible creature that is reachable * trough the given creature from the given LegionInfo (can be null). * @param cre The recruiting creature. * @param legion The recruiting legion or null. * @return The best possible recruit. */ public CreatureType getBestPossibleRecruitEver(String cre, Legion legion) { CreatureType best = getVariant().getCreatureByName(cre); int maxVP = -1; List<RecruitVertex> all = traverse(cre, legion); Iterator<RecruitVertex> it = all.iterator(); while (it.hasNext()) { RecruitVertex v2 = it.next(); CreatureType creature = getVariant().getCreatureByName( v2.getCreatureName()); int vp = (creature == null ? -1 : creature.getPointValue()); if (vp > maxVP) { maxVP = vp; best = creature; } } return best; } /** * Determine if a creature given by 'lesser' could potentially * summon the higher valued creature given by 'greater' within N steps. * This is used to determine if 'lesser' is redundant for mustering purposes * if we have 'greater' * Here we limit the search to 'distance' (typically 2) recruit steps * since otherwise every creature * is 'reachable' via a downmuster at the tower and starting all over which * is not what we are interested in. * @param lesser Name of the recruiting creature. * @param greater Name of the recruit we are trying to get to * @param distance number of steps to consider */ public boolean isRecruitDistanceLessThan(String lesser, String greater, int distance) { List<RecruitVertex> all = traverse(lesser, null); Iterator<RecruitVertex> it = all.iterator(); // Log.debug("isReachable('" + lesser + "', '" + greater + "')"); int steps = 0; // distance including self - i.e. distance + 1 while (it.hasNext() && steps < distance + 1) { RecruitVertex v2 = it.next(); String name = v2.getCreatureName(); // Log.debug("isReachable: '" + name + "'"); if (name.compareTo(greater) == 0) { // Log.debug("Matched"); return true; } else { // Log.debug("Unmatched"); } steps++; } return false; } }