package net.sf.colossus.game;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import net.sf.colossus.variant.CreatureType;
import net.sf.colossus.variant.MasterBoardTerrain;
import net.sf.colossus.variant.MasterHex;
public abstract class Legion
{
/**
* A comparator to order legions by points, with Titan armies first.
*
* This only works properly if all legions are owned by the same player.
* The case of two legions with titans is not handled.
*
* WARNING: This is not consistent with equals() since two legions with
* the same point value are considered equal.
*/
public static final Comparator<Legion> ORDER_TITAN_THEN_POINTS = new Comparator<Legion>()
{
/**
* Legions are sorted in descending order of known total point value,
* with the titan legion always coming first.
*
* Really only useful for comparing legions of one player.
*/
public int compare(Legion o1, Legion o2)
{
if (o1.hasTitan())
{
return Integer.MIN_VALUE;
}
else if (o2.hasTitan())
{
return Integer.MAX_VALUE;
}
else
{
return (o2.getPointValue() - o1.getPointValue());
}
}
};
/**
* A comparator to order legions by points, with Titan armies first.
* If same points, by MarkerId.
*
* This only works properly if all legions are owned by the same player.
* The case of two legions with titans is not handled.
*/
public static final Comparator<Legion> ORDER_TITAN_THEN_POINTS_THEN_MARKER = new Comparator<Legion>()
{
/**
* Legions are sorted in descending order of known total point value,
* with the titan legion always coming first, and same points by
* markerId. (Otherwise some legions could be "equals" which is not
* a good idea when one wants to store them in a sorted Set.
*
* Really only useful for comparing legions of one player.
*/
public int compare(Legion o1, Legion o2)
{
int titan_then_points = ORDER_TITAN_THEN_POINTS.compare(o1, o2);
if (titan_then_points != 0)
{
return (titan_then_points);
}
else
{
return o1.getMarkerId().compareTo(o2.getMarkerId());
}
}
};
/**
* The player/game combination owning this Legion.
*
* Never null.
*/
private final Player player;
/**
* The current position of the legion on the masterboard.
*
* Never null.
*/
private MasterHex currentHex;
/**
* The creatures in this legion.
*/
private final List<Creature> creatures = new ArrayList<Creature>();
/**
* The ID of the marker of this legion.
*
* Used as identifier for serialization purposes. Never null.
*/
private final String markerId;
/**
* Flag if the legion has moved in the current masterboard round.
*/
private boolean moved;
/**
* Flag if the legion has teleported in the current masterboard round.
*/
private boolean teleported;
/**
* The side this legion entered a battle in.
*/
private EntrySide entrySide = EntrySide.NOT_SET;
protected List<AcquirableDecision> decisions = null;
protected int angelsToAcquire;
/**
* Has doSplit already been sent to server. Only needed on client side,
* but this way we avoid casting.
*/
private boolean splitRequestSent = false;
/**
* Has undoSplit already been sent to server. Only needed on client side,
* but this way we avoid casting. Marked in both parent and child.
*/
private boolean undoSplitRequestSent = false;
/**
* The creature recruited in last recruit phase
*/
private CreatureType recruit;
/**
* Flag to remember a "skip (split|move|recruit) this time"
*/
private boolean skipThisTime = false;
/**
* Flag to remember that legion has been visited this phase
*/
private boolean visitedThisPhase = false;
// TODO legions should be created through factory from the player instances
public Legion(final Player player, String markerId, MasterHex hex)
{
assert player != null : "Legion has to have a player";
assert markerId != null : "Legion has to have a markerId";
assert hex != null : "Legion nees to be placed somewhere";
this.player = player;
this.markerId = markerId;
this.currentHex = hex;
}
/**
* Retrieves the player this legion belongs to.
*
* @return The matching player. Never null.
*/
public Player getPlayer()
{
return player;
}
/**
* Places the legion into the new position.
*
* @param newPosition the hex that will be the new position. Not null.
* @see #getCurrentHex()
*/
public void setCurrentHex(MasterHex newPosition)
{
assert newPosition != null : "Need position to move legion to";
this.currentHex = newPosition;
}
/**
* Returns the current position of the legion.
*
* @return the hex the legion currently is on.
*
* @see #setCurrentHex(MasterHex)
*/
public MasterHex getCurrentHex()
{
assert currentHex != null : "getCurrentHex() called on Legion before position was set";
return currentHex;
}
/**
* TODO should be an unmodifiable List, but can't at the moment since both
* derived classes and users might still expect to change it
* TODO should be List<Creature>, but subtypes are still covariant
*/
public List<? extends Creature> getCreatures()
{
return creatures;
}
public String getMarkerId()
{
return markerId;
}
public String getLongMarkerId()
{
return markerId + "-" + getPlayer().getColor().getName();
}
public boolean hasTitan()
{
for (Creature critter : getCreatures())
{
if (critter.getType().isTitan())
{
return true;
}
}
return false;
}
/**
* @return returns the Titan Creature, if this legions has the titan,
* or null if it hasn't.
*/
public Creature getTitan()
{
for (Creature critter : getCreatures())
{
if (critter.getType().isTitan())
{
return critter;
}
}
return null;
}
/**
* Returns the number of creatures in this legion.
*
* @return the number of creatures in the legion
*/
public int getHeight()
{
return getCreatures().size();
}
public void setSplitRequestSent(boolean val)
{
this.splitRequestSent = val;
}
public boolean getSplitRequestSent()
{
return this.splitRequestSent;
}
public void setUndoSplitRequestSent(boolean val)
{
this.undoSplitRequestSent = val;
}
public boolean getUndoSplitRequestSent()
{
return this.undoSplitRequestSent;
}
public void setMoved(boolean moved)
{
this.moved = moved;
this.skipThisTime = false;
}
public boolean hasMoved()
{
return moved;
}
public void setTeleported(boolean teleported)
{
this.teleported = teleported;
}
public boolean hasTeleported()
{
return teleported;
}
public void setSkipThisTime(boolean skipIt)
{
this.skipThisTime = skipIt;
}
public boolean getSkipThisTime()
{
return skipThisTime;
}
public void setVisitedThisPhase(boolean visited)
{
this.visitedThisPhase = visited;
}
public boolean getVisitedThisPhase()
{
return visitedThisPhase;
}
public boolean contains(CreatureType type)
{
return getCreatureTypes().contains(type);
}
public abstract void addCreature(CreatureType type);
public abstract void removeCreature(CreatureType type);
public void setEntrySide(EntrySide entrySide)
{
this.entrySide = entrySide;
}
public EntrySide getEntrySide()
{
return entrySide;
}
/**
* TODO unify between the two derived classes if possible -- the handling of Titans
* is quite different, although it should have the same result
*/
public abstract int getPointValue();
public CreatureType getRecruit()
{
return recruit;
}
public void setRecruit(CreatureType recruit)
{
this.recruit = recruit;
}
public boolean hasRecruited()
{
return (recruit != null);
}
public boolean hasSummonable()
{
for (Creature creature : getCreatures())
{
if (creature.getType().isSummonable())
{
return true;
}
}
return false;
}
public boolean canFlee()
{
for (Creature critter : getCreatures())
{
if (critter.getType().isLord())
{
return false;
}
}
return true;
}
public int numCreature(CreatureType creatureType)
{
int count = 0;
for (Creature critter : getCreatures())
{
if (critter.getType().equals(creatureType))
{
count++;
}
}
return count;
}
public int numLords()
{
int count = 0;
for (Creature critter : getCreatures())
{
if (critter.getType().isLord())
{
count++;
}
}
return count;
}
public int numRangestrikers()
{
int count = 0;
for (Creature critter : getCreatures())
{
if (critter.getType().isRangestriker())
{
count++;
}
}
return count;
}
/**
* Calculate the acquirableDecisions and store them in the legion.
* @param score
* @param points
*/
public void setupAcquirableDecisions(int score, int points)
{
this.decisions = calculateAcquirableDecisions(score, points);
}
/**
* From the given score, awarding given points, calculate the choices for
* each threshold that will be crossed. E.g. 375+150 => 525 will cross
* 400 and 500, so one has to make two decisions:
* 400: take angel (or not);
* 500: take angel, archangel (or nothing).
* This only calculates them, does not set them in the legion yet; so a client
* or AI could use this for theoretical calculations "how much / which angels
* would I get if..." without modifying the legions state itself.
* The limits for "which one can get" due to legion height, creatures left count
* and terrain are considered (implicitly, because findEligibleAngels(tmpScore)
* checks them).
*
* @param score Current score of player
* @param points Points to be added which entitle to acquiring
* @return List of decisions
*/
List<AcquirableDecision> calculateAcquirableDecisions(int score, int points)
{
ArrayList<AcquirableDecision> acquirablesDecisions = new ArrayList<AcquirableDecision>();
// Example: Start with (score) 375, earn (points) 150 => 525
int value = player.getGame().getVariant()
.getAcquirableRecruitmentsValue();
// 100
int tmpScore = score; // 375
int tmpPoints = points; // 150
// round Score down, and tmpPoints by the same amount.
// this allow to keep all points
int round = (tmpScore % value); // 75
tmpScore -= round; // 300
tmpPoints += round; // 225
List<CreatureType> recruits;
// @TODO: the constraint by height would make only sense, if askAquire's
// would be fired sequentially - 2nd not before decision response for 1st
// from client did arrive. Right now they are fired all at same time
// (and have to), because client (e.g. human player) should get all of the
// choices at once, to take e.g. the 500 archangel and skip the 400 angel.
// AI does not handle that well yet, and humans get several modal dialogs.
// Should be improved generally...
while (tmpPoints >= value)
{
tmpScore += value; // 400 500
tmpPoints -= value; // 125 25
recruits = findEligibleAngels(tmpScore);
if ((recruits != null) && (!recruits.isEmpty()))
{
AcquirableDecision decision = new AcquirableDecision(this,
tmpScore, recruits);
// {legion, 400, [Angel]} {legion, 500, [Angel, Archangel]}
acquirablesDecisions.add(decision); // [d1] [d1, d2]
angelsToAcquire++; // 1 2
}
}
return acquirablesDecisions; // 2 decisions
}
/**
* Retrieves a list of all creature types in this legion.
*
* This matches getCreatures() but lists the types instead of the
* individual creatures.
*
* @return A list of all creature types in this legion.
*/
public List<CreatureType> getCreatureTypes()
{
List<CreatureType> result = new ArrayList<CreatureType>();
for (Creature creature : getCreatures())
{
result.add(creature.getType());
}
return result;
}
/**
* Calculate which angels this legion can get in its current land
* when crossing the given points threshold
*
* @param points Score threshold (100, ..., 400, 500) for which to get angel
* @return list of creatures that can be get at that threshold
*/
public List<CreatureType> findEligibleAngels(int points)
{
MasterBoardTerrain terrain = getCurrentHex().getTerrain();
return player.getGame().findAvailableEligibleAngels(terrain, points);
}
/**
* Returns the markerId for debug and serialisation purposes.
*
* Since this is relevant for the network protocol, the method
* is declared final.
*/
@Override
public final String toString()
{
return getMarkerId();
}
@Override
public final int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result
+ ((markerId == null) ? 0 : markerId.hashCode());
return result;
}
/**
* Two legions are considered equal if they have the same marker.
*
* Even though contents may change over time, we consider two legions
* to be the same as long as they have the same marker. This notion of
* equality is used throughout the code, so we enforce it by having both
* {{@link #equals(Object)} and {@link #hashCode()} declared final.
*/
@Override
public final boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Legion other = (Legion)obj;
if (markerId == null)
{
if (other.markerId != null)
return false;
}
else if (!markerId.equals(other.markerId))
return false;
return true;
}
/**
* Data for one pending decision. For example, for crossing the 500
* there will be a decision, whether the player takes for this legion
* an angel or an archangel.
*
* TODO this should not be here, it should probably be modelled as a
* game action
*/
public class AcquirableDecision
{
private final Legion legion;
private final int points;
private final List<CreatureType> acquirables;
public AcquirableDecision(Legion legion, int points,
List<CreatureType> acquirables)
{
this.legion = legion;
this.points = points;
this.acquirables = acquirables;
}
public List<CreatureType> getAcquirables()
{
return acquirables;
}
// so far not used anywhere
public int getPoints()
{
return points;
}
// so far not used anywhere
public Legion getLegion()
{
return legion;
}
}
}