package net.sf.colossus.variant;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Logger;
import net.sf.colossus.common.Constants;
/**
* The recruiting sub-tree in a terrain (or several terrains)
* @author Romain Dolbeau
*/
public class RecruitingSubTree implements IRecruiting
{
private static final Logger LOGGER = Logger
.getLogger(RecruitingSubTree.class.getName());
private static class RecruiterAndRecruit
{
final private CreatureType recruiter;
final private CreatureType recruit;
RecruiterAndRecruit(CreatureType recruiter, CreatureType recruit)
{
this.recruiter = recruiter;
this.recruit = recruit;
}
@Override
public boolean equals(Object o)
{
if (!(o instanceof RecruiterAndRecruit))
{
return false;
}
RecruiterAndRecruit rar = (RecruiterAndRecruit)o;
return this.getRecruiter().equals(rar.getRecruiter())
&& this.getRecruit().equals(rar.getRecruit());
}
@Override
public int hashCode()
{
int hash = 3;
hash = 31
* hash
+ (this.getRecruiter() != null ? this.getRecruiter()
.hashCode() : 0);
hash = 31
* hash
+ (this.getRecruit() != null ? this.getRecruit().hashCode()
: 0);
return hash;
}
@Override
public String toString()
{
return getRecruiter().getName() + " recruits "
+ getRecruit().getName();
}
/**
* @return the recruiter
*/
public CreatureType getRecruiter()
{
return recruiter;
}
/**
* @return the recruit
*/
public CreatureType getRecruit()
{
return recruit;
}
}
private final Map<RecruiterAndRecruit, Integer> regular = new HashMap<RecruiterAndRecruit, Integer>();
private final Map<CreatureType, Integer> any = new HashMap<CreatureType, Integer>();
private final Map<CreatureType, Integer> anyNonLord = new HashMap<CreatureType, Integer>();
private final Map<CreatureType, Integer> anyLord = new HashMap<CreatureType, Integer>();
private final Map<CreatureType, Integer> anyDemiLord = new HashMap<CreatureType, Integer>();
private final Set<ICustomRecruitBase> allCustom = new HashSet<ICustomRecruitBase>();
private final Set<CreatureType> allRecruits = new TreeSet<CreatureType>();
private boolean completed = false;
private final AllCreatureType creatureTypes;
public RecruitingSubTree(AllCreatureType creatureTypes)
{
this.creatureTypes = creatureTypes;
}
@Override
public String toString()
{
StringBuffer buf = new StringBuffer();
for (RecruiterAndRecruit rar : regular.keySet())
{
buf.append(rar.toString());
buf.append(" gives ");
buf.append(regular.get(rar));
buf.append("\n");
}
for (CreatureType ct : any.keySet())
{
buf.append("Any ");
buf.append(any.get(ct));
buf.append(" recruits ");
buf.append(ct.getName());
buf.append("\n");
}
for (CreatureType ct : anyNonLord.keySet())
{
buf.append("Any ");
buf.append(anyNonLord.get(ct));
buf.append(" non lord recruits ");
buf.append(ct.getName());
buf.append("\n");
}
for (CreatureType ct : anyLord.keySet())
{
buf.append("Any ");
buf.append(anyLord.get(ct));
buf.append(" lord recruits ");
buf.append(ct.getName());
buf.append("\n");
}
for (CreatureType ct : anyDemiLord.keySet())
{
buf.append("Any ");
buf.append(anyDemiLord.get(ct));
buf.append(" demilord recruits ");
buf.append(ct.getName());
buf.append("\n");
}
for (ICustomRecruitBase crb : allCustom)
{
buf.append("Custom by class " + crb.getClass().getName());
buf.append("\n");
}
return buf.toString();
}
private boolean isRegularAncestorOf(final CreatureType a,
final CreatureType b, final Set<CreatureType> checked)
{
if (regular.containsKey(new RecruiterAndRecruit(a, b)))
{
return true;
}
for (RecruiterAndRecruit rar : regular.keySet())
{
final CreatureType c = rar.getRecruiter();
final CreatureType d = rar.getRecruit();
if (c.equals(a) && !checked.contains(d))
{
Set<CreatureType> checked2 = new HashSet<CreatureType>(checked);
checked2.add(a);
if (isRegularAncestorOf(d, b, checked2))
{
return true;
}
}
}
return false;
}
private void completeGraph()
{
Set<CreatureType> allInvolved = new HashSet<CreatureType>();
/* first, extract all creature really used (recruiter or recruit) in this terrain */
for (RecruiterAndRecruit rar : regular.keySet())
{
// Titan should never been involved in the regular recruit graph
// except at the very beginning.
if (!rar.getRecruiter().isTitan())
allInvolved.add(rar.getRecruiter());
if (!rar.getRecruit().isTitan())
allInvolved.add(rar.getRecruit());
else
{
LOGGER.warning("TITAN as regular recruit ????");
LOGGER.warning(this.toString());
}
}
Set<RecruiterAndRecruit> extra = new HashSet<RecruiterAndRecruit>();
/* then for all combinations, check which are ancestor to which other
* in the tree. This information is stored in an intermediate set,
* otherwise we would add artificial ancestors
*/
for (CreatureType a : allInvolved)
{
for (CreatureType b : allInvolved)
{
RecruiterAndRecruit rar = new RecruiterAndRecruit(a, b);
if (!regular.containsKey(rar))
{
Set<CreatureType> checked = new HashSet<CreatureType>();
checked.add(b);
if (isRegularAncestorOf(b, a, checked))
{
LOGGER.finest("Completing: adding " + a.getName()
+ " to " + b.getName());
extra.add(rar);
}
}
}
}
/* finally, a creature can recruit all its ancestor with 1 */
for (RecruiterAndRecruit rar : extra)
{
addRegular(rar.getRecruiter(), rar.getRecruit(), 1);
}
}
public void complete(boolean regularRecruit)
{
completed = true;
if (regularRecruit)
{
completeGraph();
}
}
public void addRegular(CreatureType recruiter, CreatureType recruit,
int number)
{
assert recruit != null : "Oups, recruit must not be null";
assert recruiter != null : "Oups, recruiter must not be null";
assert number > 0 : "Oups, number should be > 0";
assert !recruit.isTitan() : "Oups, can't recruit Titan";
regular.put(new RecruiterAndRecruit(recruiter, recruit), new Integer(
number));
// should recruiter by a recruitable, completeGraph will take care
allRecruits.add(recruit);
}
@SuppressWarnings("boxing")
public void addAny(CreatureType recruit, int number)
{
assert completed == false : "Oups, can't add after being completed";
assert recruit != null : "Oups, recruit must not be null";
assert number >= 0 : "Oups, number should be >= 0";
assert !recruit.isTitan() : "Oups, can't recruit Titan";
any.put(recruit, number);
allRecruits.add(recruit);
}
@SuppressWarnings("boxing")
public void addNonLord(CreatureType recruit, int number)
{
assert completed == false : "Oups, can't add after being completed";
assert recruit != null : "Oups, recruit must not be null";
assert number > 0 : "Oups, number should be > 0";
assert !recruit.isTitan() : "Oups, can't recruit Titan";
anyNonLord.put(recruit, number);
allRecruits.add(recruit);
}
@SuppressWarnings("boxing")
public void addLord(CreatureType recruit, int number)
{
assert completed == false : "Oups, can't add after being completed";
assert recruit != null : "Oups, recruit must not be null";
assert number > 0 : "Oups, number should be > 0";
assert !recruit.isTitan() : "Oups, can't recruit Titan";
anyLord.put(recruit, number);
allRecruits.add(recruit);
}
@SuppressWarnings("boxing")
public void addDemiLord(CreatureType recruit, int number)
{
assert completed == false : "Oups, can't add after being completed";
assert recruit != null : "Oups, recruit must not be null";
assert number > 0 : "Oups, number should be > 0";
assert !recruit.isTitan() : "Oups, can't recruit Titan";
anyDemiLord.put(recruit, number);
allRecruits.add(recruit);
}
public void addCustom(ICustomRecruitBase crb)
{
assert completed == false : "Oups, can't add after being completed";
assert crb != null : "Oups, ICustomRecruitBase must not be null";
allCustom.add(crb);
}
public int numberOfRecruiterNeeded(CreatureType recruiter,
CreatureType recruit, MasterHex hex)
{
int number = Constants.BIGNUM;
/* WARNING: do not call getPossibleRecruits() from here,
* even if this look hackish.
* getPossibleRecruits() can provoke a Caretaker update,
* which tries to push informations to clients. If this
* has been called from a client, /deadlock/
*/
if ((hex == null) && !allRecruits.contains(recruit))
{
return number;
}
/* LOGGER.finest("Start for recruiter and recruit : " +
recruiter.getName() + " & " + recruit.getName()); */
if (recruiter.equals(recruit))
{
LOGGER.finest("Recruiter and recruit are identical = 1 "
+ recruiter.getName() + " & " + recruit.getName());
number = 1;
}
RecruiterAndRecruit rar = new RecruiterAndRecruit(recruiter, recruit);
if (regular.keySet().contains(rar))
{
LOGGER.finest("Recruiter and recruit are regular = "
+ regular.get(rar) + " " + recruiter.getName() + " & "
+ recruit.getName());
number = Math.min(number, regular.get(rar).intValue());
}
if (any.keySet().contains(recruit))
{
LOGGER.finest("Recruit in any = " + regular.get(rar) + " "
+ recruit.getName());
number = Math.min(number, any.get(recruit).intValue());
}
if (!recruiter.isLord() && !recruiter.isDemiLord())
{
if (anyNonLord.keySet().contains(recruit))
{
LOGGER.finest("Recruit in anyNonLord = " + regular.get(rar)
+ " " + recruit.getName());
number = Math.min(number, anyNonLord.get(recruit).intValue());
}
}
if (recruiter.isLord())
{
if (anyLord.keySet().contains(recruit))
{
LOGGER.finest("Recruit in anyLord = " + regular.get(rar) + " "
+ recruit.getName());
number = Math.min(number, anyLord.get(recruit).intValue());
}
}
if (recruiter.isDemiLord())
{
if (anyDemiLord.keySet().contains(recruit))
{
LOGGER.finest("Recruit in anyDemiLord = " + regular.get(rar)
+ " " + recruit.getName());
number = Math.min(number, anyDemiLord.get(recruit).intValue());
}
}
for (ICustomRecruitBase crb : allCustom)
{
LOGGER.finest("Checking with CRB " + crb.getClass().getName());
number = Math.min(number,
crb.numberOfRecruiterNeeded(recruiter, recruit, hex));
}
return number;
}
/** WARNING: This function, trough the CustomRecruitBase, can
* cause a caretaker update. It should not be called under circumstances
* where this update is bad.
*/
public Set<CreatureType> getPossibleRecruits(MasterHex hex)
{
Set<CreatureType> possibleRecruits = new TreeSet<CreatureType>();
possibleRecruits.addAll(allRecruits);
if (hex == null)
{
LOGGER.finer("Hex is null, ignoring special recruits.");
}
else
{
for (ICustomRecruitBase cri : allCustom)
{
List<CreatureType> temp = cri.getPossibleSpecialRecruits(hex);
possibleRecruits.addAll(temp);
}
}
return possibleRecruits;
}
public Set<CreatureType> getPossibleRecruiters(MasterHex hex)
{
if (!any.keySet().isEmpty())
{
return creatureTypes.getCreatureTypes();
}
Set<CreatureType> possibleRecruiters = new TreeSet<CreatureType>();
for (RecruiterAndRecruit rar : regular.keySet())
{
CreatureType recruit = rar.getRecruit();
if (!recruit.isTitan())
{
possibleRecruiters.add(recruit);
}
else
{
LOGGER.warning("TITAN as regular recruit ????");
LOGGER.warning(this.toString());
}
CreatureType recruiter = rar.getRecruiter();
possibleRecruiters.add(recruiter);
}
if (!anyNonLord.keySet().isEmpty())
{
for (CreatureType ct : creatureTypes.getCreatureTypes())
{
if (!ct.isLord() && !ct.isDemiLord())
{
possibleRecruiters.add(ct);
}
}
}
if (!anyLord.keySet().isEmpty())
{
for (CreatureType ct : creatureTypes.getCreatureTypes())
{
if (ct.isLord())
{
possibleRecruiters.add(ct);
}
}
}
if (!anyDemiLord.keySet().isEmpty())
{
for (CreatureType ct : creatureTypes.getCreatureTypes())
{
if (ct.isDemiLord())
{
possibleRecruiters.add(ct);
}
}
}
/* note: everyting *above* that point can be cached, but the global
* result itself cannot, as custom recruiting might change from call to
* call
*/
for (ICustomRecruitBase cri : allCustom)
{
List<CreatureType> temp = cri.getPossibleSpecialRecruiters(hex);
possibleRecruiters.addAll(temp);
}
return possibleRecruiters;
}
public int maximumNumberNeededOf(CreatureType ct, MasterHex hex)
{
int max = -1;
for (CreatureType rec : allRecruits)
{
int num = this.numberOfRecruiterNeeded(ct, rec, hex);
if ((num < Constants.BIGNUM) && (num > max))
{
max = num;
}
}
return max;
}
public static boolean isADeadEnd(Variant variant, CreatureType creature)
{
for (MasterBoardTerrain terrain : variant.getTerrains())
{
RecruitingSubTree r = (RecruitingSubTree)terrain
.getRecruitingSubTree();
for (RecruiterAndRecruit rar : r.regular.keySet())
{
if (rar.getRecruiter().equals(creature))
{
CreatureType recruit = rar.getRecruit();
if (recruit.getPointValue() > creature.getPointValue())
{
return false;
}
}
}
}
return true;
}
public static Set<CreatureType> getAllInAllSubtreesIgnoringSpecials(
Variant variant, CreatureType creature)
{
Set<CreatureType> results = new TreeSet<CreatureType>();
Map<MasterBoardTerrain, Set<CreatureType>> checked = new HashMap<MasterBoardTerrain, Set<CreatureType>>();
results.addAll(getAllInAllSubtreesIgnoringSpecialsRec(variant,
checked, creature));
return results;
}
@SuppressWarnings("boxing")
private static Set<CreatureType> getAllInAllSubtreesIgnoringSpecialsRec(
Variant variant, Map<MasterBoardTerrain, Set<CreatureType>> checked,
CreatureType creature)
{
Set<CreatureType> results = new TreeSet<CreatureType>();
for (MasterBoardTerrain terrain : variant.getTerrains())
{
if (checked.get(terrain) == null)
{
checked.put(terrain, new TreeSet<CreatureType>());
}
RecruitingSubTree r = (RecruitingSubTree)terrain
.getRecruitingSubTree();
for (RecruiterAndRecruit rar : r.regular.keySet())
{
RecruiterAndRecruit backward = new RecruiterAndRecruit(
rar.getRecruit(), rar.getRecruiter());
if (r.regular.keySet().contains(backward))
{ // don't go backward ; we assume backward is the cheapest way.
if ((r.regular.get(rar) >= 1)
&& (r.regular.get(backward) < Constants.BIGNUM)
&& (r.regular.get(rar) < r.regular.get(backward)))
{
continue;
}
// also downgrade in point is backward (this doesn't catch Troll -> Ranger)
// but it should have been catched above
// note that this still won't catch C->A if A->B, B->C, and PV(A)==PV(B)==PV(C)
if ((r.regular.get(rar) == 1)
&& (r.regular.get(backward) < Constants.BIGNUM)
&& rar.getRecruit().getPointValue() < backward
.getRecruit().getPointValue())
{
continue;
}
}
else
{
// One-way only? Then this is a more-than-on-levele backward edge.
continue;
}
if (rar.getRecruiter().equals(creature))
{
CreatureType recruit = rar.getRecruit();
results.add(recruit);
if (!checked.get(terrain).contains(recruit))
{
checked.get(terrain).add(recruit);
results.addAll(getAllInAllSubtreesIgnoringSpecialsRec(
variant, checked, recruit));
}
}
}
}
return results;
}
}