package net.sf.colossus.ai.helper;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Logger;
import net.sf.colossus.ai.AbstractAI;
import net.sf.colossus.client.CritterMove;
import net.sf.colossus.common.Options;
import net.sf.colossus.util.DevRandom;
import net.sf.colossus.util.ErrorUtils;
import net.sf.colossus.variant.BattleHex;
/**
* On-the-fly generation of the Collection of all possible LegionMove.
* This doesn't fully respect the Collection interface:
* The random generation may fail before all elements have been returned,
* so to iterators may return different subsets of the entire collection.
* Also, this may cause size() to return a value higher than what is really
* accessible.
*
* @author Romain Dolbeau
*/
@SuppressWarnings("boxing")
public class OnTheFlyLegionMove implements Collection<LegionMove>
{
/** Maximum number of try before giving up generating a new element.
* Ideally this is only a safety belt.
*/
final static private int RANDOM_MAX_TRY = 50;
/** number of elements to put in each new batch of element.
* From my experiments, should be about 1-3 second worth of evaluation.
*/
final static private int REFILL_SIZE = 2000;
/* genetic stuff */
/** Percentage that a gene will be random instead of inherited.
* Low will densify exploration around the current maximums.
* High will widen the explorated space around the current maximums.
*/
final static private int RANDOM_GENE_PERCENT = 10;
/** Percentage of a randomly chosen parent.
* Another parameter to avoid inbreeding and missing not-yet-detected
* local maximums.
*/
final static private int RANDOM_PARENT_PERCENT = 10;
/** Percentage from the top (of the already avaluated space) to pick
* a 'good' parent.
* Low will pick parent only from very near the local maximums.
* High will give not-so-good parents a chance.to breed.
*/
final static private int GOOD_PARENT_TOP_PERCENT = 20;
/** Minimum number of possible 'good' parents.
* For small exploration space, this avoid excessive inbreeding.
*/
final static private int MIN_PARENT_CHOICE = 50;
/** Percentage of fully random new elements
* This helps diversifying the gene pool.
*/
final static private int SPONTANEOUS_GENERATION_PERCENT = 5;
/** Amount of memory needed before a refill.
* This avoid crashing low-mem JVM.
* The constant part is of the pulled-out-of-a-hat variety.
*/
final static private long MIN_MEMORY_REFILL = 10 * 1024 * REFILL_SIZE;
private static final Logger LOGGER = Logger
.getLogger(OnTheFlyLegionMove.class.getName());
private final List<List<CritterMove>> allCritterMoves;
private final int mysize;
public OnTheFlyLegionMove(final List<List<CritterMove>> acm)
{
allCritterMoves = acm;
long realcount = 1;
for (List<CritterMove> lcm : allCritterMoves)
{
realcount *= lcm.size();
}
if (realcount > Integer.MAX_VALUE)
{
mysize = Integer.MAX_VALUE;
}
else
{
mysize = (int)realcount;
}
LOGGER.finest("OnTheFlyLegionMove created for " + realcount
+ " combinations"
+ (mysize != realcount ? " limited to " + mysize : ""));
}
int getDim()
{
return allCritterMoves.size();
}
class OnTheFlyLegionMoveIterator implements Iterator<LegionMove>
{
String intArrayToString(int[] t)
{
StringBuffer buf = new StringBuffer();
buf.append(t[0]);
for (int i = 1; i < t.length; i++)
{
buf.append(" " + t[i]);
}
return buf.toString();
}
class myIntArrayComparator implements Comparator<int[]>
{
long baseXvalue(int[] t)
{
long temp = 0;
long factor = 1;
/* interpret the array as a base-X number */
/* we have 27 hexes and change, this should fit */
for (int i = 0; i < t.length; i++)
{
temp += t[i] * factor;
factor *= daddy.allCritterMoves.get(i).size();
}
return temp;
}
int[] roundNextUp(int[] t, int factor)
{
int[] temp = new int[t.length];
for (int i = 0; i < t.length; i++)
{
if (i < factor)
{
temp[i] = 0;
}
else
{
temp[i] = t[i];
}
}
return nextValue(temp, factor);
}
int[] nextValue(int[] t)
{
return nextValue(t, 0);
}
int[] nextValue(int[] t, int factor)
{
int[] temp = new int[t.length];
for (int i = 0; i < t.length; i++)
{
temp[i] = t[i];
}
int j = factor;
boolean ok = false;
while (!ok && j < t.length)
{
int size = daddy.allCritterMoves.get(j).size();
temp[j]++;
if (temp[j] < size)
{
ok = true;
}
else
{
temp[j] -= size;
j++;
}
}
if (j == t.length)
{
return null;
}
return temp;
}
public int compare(int[] t1, int[] t2)
{
if (t1.length > t2.length)
{
return 1;
}
if (t1.length < t2.length)
{
return -1;
}
long temp = baseXvalue(t1) - baseXvalue(t2);
if (temp > Integer.MAX_VALUE)
{
temp = Integer.MAX_VALUE;
}
if (temp < Integer.MIN_VALUE)
{
temp = Integer.MIN_VALUE;
}
return (int)temp;
}
}
class myIntArrayLegionValueComparator extends myIntArrayComparator
{
@Override
public int compare(int[] t1, int[] t2)
{
int v1 = alreadydone.get(t1).getValue();
int v2 = alreadydone.get(t2).getValue();
if (v1 > v2)
{
return 1;
}
if (v2 > v1)
{
return -1;
}
return super.compare(t1, t2);
}
}
/** map from indexes to LegionMove, what we have already sent to the AI */
private final SortedMap<int[], LegionMove> alreadydone = new TreeMap<int[], LegionMove>(
new myIntArrayComparator());
/** already done & evaluated, sorted by legion value */
private final List<int[]> byValues = new ArrayList<int[]>();
private final myIntArrayLegionValueComparator byValuesComparator = new myIntArrayLegionValueComparator();
/** the previously returned object */
private int[] lastone = null;
/** map from indexes to LegionMove, the next batch to send to the AI */
private final SortedMap<int[], LegionMove> beingdone = new TreeMap<int[], LegionMove>(
new myIntArrayComparator());
private final OnTheFlyLegionMove daddy;
private final Random rand = new DevRandom();
private final int dim;
private boolean abort = false;
private boolean failoverOnly = false;
/** The 'incompatibility map'.
* first index is which position in the source int[] is checked.
* second index is which position in the destination source int[] is checked.
* third index is value in the source int[].
* The Set is all the incompatible values in the dest int[].
*/
private final Set<Integer>[][][] incomps;
@SuppressWarnings("unchecked")
OnTheFlyLegionMoveIterator(OnTheFlyLegionMove d)
{
daddy = d;
dim = daddy.getDim();
incomps = new Set[dim][dim][30];//never more than 30 hexes ???
buildIncompMap();
firstfill();
}
private void buildIncompMap()
{
LOGGER.finest("BuildIncompMap started");
for (int i = 0; i < dim; i++)
{
List<CritterMove> li = daddy.allCritterMoves.get(i);
for (int j = 0; j < dim; j++)
{
if (i != j)
{
List<CritterMove> lj = daddy.allCritterMoves.get(j);
for (int k = 0; k < li.size(); k++)
{
BattleHex a = li.get(k).getEndingHex();
Set<Integer> s = new TreeSet<Integer>();
incomps[i][j][k] = s;
if (!a.isEntrance())
{
for (int l = 0; l < lj.size(); l++)
{
BattleHex b = lj.get(l).getEndingHex();
if (a.equals(b))
{
s.add(l);
}
}
}
}
}
}
}
LOGGER.finest("BuildIncompMap done");
}
public boolean hasNext()
{
if (lastone != null)
{ // the previously returned value has been evaluated now
// so we can put it in the byvalues set.
byValues.add(lastone);
lastone = null;
}
if (beingdone.isEmpty() && !abort)
{
int real_refill_size = REFILL_SIZE;
long freemem = Runtime.getRuntime().freeMemory();
LOGGER.finest("Memory available (before GC) = " + freemem
+ " bytes (" + freemem / (1024 * 1024) + " MiB)");
Runtime.getRuntime().gc();
freemem = Runtime.getRuntime().freeMemory();
LOGGER.finest("Memory available (after GC) = " + freemem
+ " bytes (" + freemem / (1024 * 1024) + " MiB)");
/* Don't refill is there's not much memory left. What will
* already have will do
*/
if (freemem < (MIN_MEMORY_REFILL))
{
// Yeah, I know about that memory issue, and really don't
// want them to spoil my stresstest statistics...
if (!Options.isStresstest())
{
LOGGER.warning("Memory is still low (" + freemem
+ " bytes), no more refill.");
}
}
else
{
/* If we're a bit short on memory, do smaller refill
* so that we don't overuse the memory.
* Also, this avoids spending too much time in the GC
* next time.
*/
if (freemem < (2 * MIN_MEMORY_REFILL))
{
real_refill_size /= 6;
}
else if (freemem < (3 * MIN_MEMORY_REFILL))
{
real_refill_size /= 4;
}
else if (freemem < (4 * MIN_MEMORY_REFILL))
{
real_refill_size /= 3;
}
else if (freemem < (6 * MIN_MEMORY_REFILL))
{
real_refill_size /= 2;
}
refill(real_refill_size);
}
}
return (!beingdone.isEmpty());
}
private int higherRankIncomp(int[] indexes)
{
/* not i >= 0, because we return 0 anyway, so why waste time
* checking ?*/
for (int i = dim - 1; i > 0; i--)
{
for (int j = dim - 1; j > i; j--)
{
Set<Integer> inc = incomps[i][j][indexes[i]];
if (inc.contains(indexes[j]))
{
return i;
}
}
}
return 0;
}
private boolean isBad(int[] indexes)
{
boolean isBad = false;
for (int i = 0; i < dim && !isBad; i++)
{
for (int k = 0; k < i && !isBad; k++)
{
Set<Integer> inc = incomps[i][k][indexes[i]];
if (inc.contains(indexes[k]))
{
isBad = true;
}
}
}
return isBad;
}
/** full recursive generation */
private int recurseGenerate(int index, int[] counts, int[] actual)
{
int total = 0;
if (index < dim)
{
for (int i = 0; i < counts[index]; i++)
{
actual[index] = i;
total += recurseGenerate(index + 1, counts, actual);
}
}
else
{
total = 1;
int[] indexes = new int[dim];
for (int i = 0; i < dim; i++)
{
indexes[i] = actual[i];
}
if (!isBad(indexes))
{
if (!beingdone.keySet().contains(indexes))
{
LegionMove current = AbstractAI.makeLegionMove(
indexes, daddy.allCritterMoves);
beingdone.put(indexes, current);
//LOGGER.finest("Generated a good one.");
}
}
else
{
//LOGGER.finest("Tested combination was bad.");
}
}
return total;
}
/** fill beingdone with the first, supposedly most interesting
* combinatione.
* @return The number of combinations generated.
*/
private int firstfill()
{
int[] counts = new int[dim];
int[] actual = new int[dim];
int total = 0;
for (int i = 0; i < dim; i++)
{
actual[i] = 0;
counts[i] = i + 2;
if (counts[i] > daddy.allCritterMoves.get(i).size())
{
counts[i] = daddy.allCritterMoves.get(i).size();
}
}
total = recurseGenerate(0, counts, actual);
int count = beingdone.keySet().size();
LOGGER.finer("Firstfill generated " + count + " out of " + total
+ " checked");
if (count <= 0)
{
LOGGER
.warning("Firstfill generated 0 combinations. This is bad.");
}
return count;
}
/** deterministically make up a on-used combination */
private int[] lastDense = null;
private int[] failoverGeneration()
{
//LOGGER.finest("failoverGeneration start");
// int count = 0;
int[] temp = new int[dim];
if (lastDense == null)
{
lastDense = new int[dim];
}
for (int i = 0; i < dim; i++)
{
temp[i] = lastDense[i];
}
myIntArrayComparator comp = new myIntArrayComparator();
while (temp != null
&& (alreadydone.keySet().contains(temp)
|| beingdone.keySet().contains(temp) || isBad(temp)))
{
/* check if some of the higher index positions are
* incompatible. If yes, try to break the higher index
* incompatibility.
* In practice the then branch could be folded into the
* else branch, as they do the same thing for hr == 0.
*/
// count++;
int hr = higherRankIncomp(temp);
if (hr == 0)
{
temp = comp.nextValue(temp);
}
else
{
temp = comp.roundNextUp(temp, hr);
}
}
if (temp != null)
{
for (int i = 0; i < dim; i++)
{
lastDense[i] = temp[i];
}
}
//LOGGER.finest("failoverGeneration done after " + count + " attempts.");
return temp;
}
/** create a fully random combination */
private int[] spontaneousGeneration()
{
int[] child = new int[dim];
for (int i = 0; i < dim; i++)
{
child[i] = rand.nextInt(daddy.allCritterMoves.get(i).size());
}
return child;
}
/** create a genetic combination */
private int[] geneticGeneration()
{
int[] mom = getParent(RANDOM_PARENT_PERCENT,
GOOD_PARENT_TOP_PERCENT);
int[] dad = getParent(RANDOM_PARENT_PERCENT,
GOOD_PARENT_TOP_PERCENT);
if ((mom == null) || (dad == null))
{
return null;
}
int[] child = breed(mom, dad, RANDOM_GENE_PERCENT);
return child;
}
/** pick a parent */
private int[] getParent(int percentRandom, int percentTop)
{
int[] parent;
int length = byValues.size();
if (length <= 0)
{
LOGGER
.warning("getParent called but byValues has no element.");
System.err.println("Dumping....");
Thread.dumpStack();
for (int i = 0; i < daddy.allCritterMoves.size(); i++)
{
System.err.println("# of moves @ " + i + " = "
+ daddy.allCritterMoves.get(i).size());
for (int j = 0; j < daddy.allCritterMoves.get(i).size(); j++)
{
System.err.println("Move for " + i + "/" + j + " is "
+ daddy.allCritterMoves.get(i).get(j).toString());
}
}
ErrorUtils
.showErrorDialog(
null,
"Experimenal AI data inconsistency!",
"During AI OnTheFlyLegionMove calculation, encountered a "
+ "'getParent called but byValues has no element' "
+ "situation. Can't continue - application will exit!");
System.exit(-50);
}
if (rand.nextInt(100) < percentRandom)
{
parent = byValues.get(rand.nextInt(length));
}
else
{
int nChoice = length / 100 * percentTop;
if (nChoice < MIN_PARENT_CHOICE)
{
nChoice = MIN_PARENT_CHOICE;
}
if (nChoice > length)
{
nChoice = length;
}
parent = byValues.get(((length - nChoice) + rand
.nextInt(nChoice)));
}
return parent;
}
/** breed a combination from parents */
private int[] breed(int[] mom, int[] dad, int percentRandom)
{
if (dim != dad.length)
{
return null;
}
if (dim != mom.length)
{
return null;
}
int[] child = new int[dim];
for (int i = 0; i < dim; i++)
{
if (rand.nextInt(100) < percentRandom)
{
child[i] = rand.nextInt(daddy.allCritterMoves.get(i)
.size());
}
else
{
if (rand.nextInt(100) < 50)
{
child[i] = mom[i];
}
else
{
child[i] = dad[i];
}
}
}
return child;
}
/** fill beingdone with up to n genetically generated, not-yet-done
* combinations.
* @param n The number of requeste combinations.
* @return The number of combinations generated.
*/
private int refill(int n)
{
if (beingdone.size() > 0)
{
return 0;
}
int ngenetic = 0;
int nfailover = 0;
Collections.sort(byValues, byValuesComparator);
LOGGER.finest("Refill started ; current best score is "
+ ((byValues.size() > 0) ? ""
+ alreadydone.get(byValues.get(byValues.size() - 1))
.getValue() : "(empty!!!!)"));
/* we have n elements to make */
for (int k = 0; (k < n) && !abort; k++)
{
int[] indexes;
LegionMove current = null;
/* after too many failover, do only failovers */
if (failoverOnly)
{
indexes = failoverGeneration();
if (indexes != null)
{
current = AbstractAI.makeLegionMove(indexes,
daddy.allCritterMoves);
beingdone.put(indexes, current);
nfailover++;
}
else
{
LOGGER.finest("Even failover didn't produce a result");
abort = true;
}
}
else
{
int ntry = 0;
/* make at most ntry at generating current, using genetic */
while ((current == null) && (ntry < RANDOM_MAX_TRY)
&& (!abort))
{
boolean genetic;
ntry++;
if (rand.nextInt(100) < SPONTANEOUS_GENERATION_PERCENT)
{
indexes = spontaneousGeneration();
genetic = false;
}
else
{
indexes = geneticGeneration();
genetic = true;
}
if ((indexes != null) && !isBad(indexes))
{
if (!beingdone.keySet().contains(indexes)
&& !alreadydone.keySet().contains(indexes))
{
current = AbstractAI.makeLegionMove(indexes,
daddy.allCritterMoves);
beingdone.put(indexes, current);
}
}
if (current != null)
{
/*
LOGGER.finest("Try " + ntry + " for move #" + k +
" found something (" +
(genetic ? "genetic" : "random") + ")");
* */
if (genetic)
{
ngenetic++;
}
}
else
{
/* if all else fail, try failover */
if (ntry == RANDOM_MAX_TRY)
{
/*
LOGGER.finest("Try " + ntry + " for move #" + k +
" still hasn't found anything.");
*/
indexes = failoverGeneration();
if (indexes != null)
{
current = AbstractAI.makeLegionMove(
indexes, daddy.allCritterMoves);
beingdone.put(indexes, current);
nfailover++;
if (nfailover > n / 10)
{
failoverOnly = true;
}
}
else
{
LOGGER
.finest("Even failover didn't produce a result");
abort = true;
}
}
}
}
}
}
int count = beingdone.keySet().size();
LOGGER.finer("Refill generated " + count + " out of " + n
+ " requested ; " + ngenetic + " genetic ("
+ ((100. * ngenetic) / count) + " %) ; " + nfailover
+ " sequential (" + ((100. * nfailover) / count) + " %)");
return count;
}
public LegionMove next()
{
if (beingdone.isEmpty())
{
LOGGER.warning("next() call but beingdone is empty!");
return null;
}
int[] anext = beingdone.firstKey();
LegionMove lmnext = beingdone.get(anext);
beingdone.remove(anext);
alreadydone.put(anext, lmnext);
lastone = anext;
return lmnext;
}
public void remove()
{
throw new UnsupportedOperationException();
}
@Override
protected void finalize() throws Throwable
{
if (lastone != null)
{ // the previously returned value has been evaluated now
// so we can put it in the byvalues set.
byValues.add(lastone);
lastone = null;
}
LOGGER.finest("From our " + mysize + " combinations, "
+ byValues.size() + " we evaluated ("
+ ((100. * byValues.size()) / mysize) + " %)");
super.finalize();
}
}
public boolean add(LegionMove o)
{
//Ensures that this collection contains the specified element (optional operation).
throw new UnsupportedOperationException();
}
public boolean addAll(Collection<? extends LegionMove> c)
{
//Adds all of the elements in the specified collection to this collection (optional operation).
throw new UnsupportedOperationException();
}
public void clear()
{
//Removes all of the elements from this collection (optional operation).
throw new UnsupportedOperationException();
}
public boolean contains(Object o)
{
// Returns true if this collection contains the specified element.
LOGGER.warning(" should be implemented ...");
return false;
}
public boolean containsAll(Collection<?> c)
{
//Returns true if this collection contains all of the elements in the specified collection.
LOGGER.warning(" should be implemented ...");
return false;
}
@Override
public boolean equals(Object o)
{
//Compares the specified object with this collection for equality.
LOGGER.warning(" should be implemented ...");
return false;
}
@Override
public int hashCode()
{
//Returns the hash code value for this collection.
return super.hashCode();
}
public boolean isEmpty()
{
//Returns true if this collection contains no elements.
return allCritterMoves.isEmpty();
}
public Iterator<LegionMove> iterator()
{
//Returns an iterator over the elements in this collection.
return new OnTheFlyLegionMoveIterator(this);
}
public boolean remove(Object o)
{
// Removes a single instance of the specified element from this collection, if it is present (optional operation).
throw new UnsupportedOperationException();
}
public boolean removeAll(Collection<?> c)
{
// Removes all this collection's elements that are also contained in the specified collection (optional operation).
throw new UnsupportedOperationException();
}
public boolean retainAll(Collection<?> c)
{
//Retains only the elements in this collection that are contained in the specified collection (optional operation).
throw new UnsupportedOperationException();
}
public int size()
{
//Returns the number of elements in this collection.
return mysize;
}
public Object[] toArray()
{
//Returns an array containing all of the elements in this collection.
LOGGER.warning(" should be implemented ...");
return null;
}
public <T> T[] toArray(T[] a)
{
//Returns an array containing all of the elements in this collection; the runtime type of the returned array is that of the specified array.
LOGGER.warning(" should be implemented ...");
return null;
}
}