package net.demilich.metastone.game.behaviour.threat.cuckoo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import net.demilich.metastone.game.behaviour.threat.FeatureVector;
import net.demilich.metastone.game.behaviour.threat.WeightedFeature;
import net.demilich.metastone.game.decks.Deck;
import net.demilich.metastone.utils.MathUtils;
public class CuckooLearner {
private static int POPULATION_SIZE = 15;
private static double DISCOVERY_RATE = 0.25;
private static double levyClamped(double base, double min, double max) {
int sign = Math.random() < 0.5 ? -1 : 1;
double value = base + sign * MathUtils.levy(1, 2);
return MathUtils.clamp(value, min, max);
}
private static FeatureVector levyFlight(FeatureVector base) {
FeatureVector variant = base.clone();
Map<WeightedFeature, Double> values = base.getValues();
for (WeightedFeature feature : values.keySet()) {
Double weight = values.get(feature);
switch (feature) {
case MINION_ATTACK_FACTOR:
case MINION_DEFAULT_TAUNT_MODIFIER:
case MINION_YELLOW_TAUNT_MODIFIER:
case MINION_RED_TAUNT_MODIFIER:
case MINION_DIVINE_SHIELD_MODIFIER:
case MINION_INTRINSIC_VALUE:
variant.set(feature, levyClamped(weight, -10, 100));
break;
case MINION_HP_FACTOR:
case MINION_SPELL_POWER_MODIFIER:
case MINION_STEALTHED_MODIFIER:
case MINION_UNTARGETABLE_BY_SPELLS_MODIFIER:
case MINION_WINDFURY_MODIFIER:
case HARD_REMOVAL_VALUE:
variant.set(feature, levyClamped(weight, 0, 100));
break;
case OWN_CARD_COUNT:
case OWN_HP_FACTOR:
case OPPONENT_HP_FACTOR:
case OPPONENT_CARD_COUNT:
variant.set(feature, levyClamped(weight, -100, 100));
break;
case RED_MODIFIER:
variant.set(feature, levyClamped(weight, -100, 0));
break;
case YELLOW_MODIFIER:
variant.set(feature, levyClamped(weight, -100, 10));
break;
default:
break;
}
}
return variant;
}
private static CuckooAgent newRandomSolution() {
FeatureVector solution = levyFlight(FeatureVector.getDefault());
return new CuckooAgent(solution);
}
private final List<CuckooAgent> nests;
private final IFitnessFunction fitnessFunction;
private CuckooAgent fittest;
public CuckooLearner(Deck deckToTrain, List<Deck> decks) {
nests = new ArrayList<CuckooAgent>(POPULATION_SIZE);
for (int i = 0; i < POPULATION_SIZE; i++) {
nests.add(newRandomSolution());
}
fittest = nests.get(0);
fitnessFunction = new WinRateFitness(deckToTrain, decks);
}
public void evolve() {
// pick a random solution
int i = ThreadLocalRandom.current().nextInt(POPULATION_SIZE);
// change it randomly
System.out.println("Random nest picked: " + i);
FeatureVector newBreed = levyFlight(nests.get(i).getData());
CuckooAgent cuckoo = new CuckooAgent(newBreed);
double fitnessI = fitnessFunction.evaluate(newBreed);
System.out.println("Fitness I: " + fitnessI);
cuckoo.setFitness(fitnessI);
int j = ThreadLocalRandom.current().nextInt(POPULATION_SIZE);
CuckooAgent randomNest = nests.get(j);
double fitnessJ = fitnessFunction.evaluate(randomNest.getData());
System.out.println("Fitness J: " + fitnessJ);
randomNest.setFitness(fitnessJ);
// if new solution is better than old one, replace it
if (fitnessI > fitnessJ) {
System.out.println("New solution is better! Replacing old one...");
nests.remove(j);
nests.add(cuckoo);
}
// rank all existing solutions
for (CuckooAgent nest : nests) {
nest.setFitness(fitnessFunction.evaluate(nest.getData()));
}
Collections.sort(nests);
// discard a portion of the worst ones
int discardAmount = (int) (POPULATION_SIZE * DISCOVERY_RATE);
for (int k = 0; k < discardAmount; k++) {
CuckooAgent notFitEnough = nests.remove(nests.size() - 1);
System.out.println("Solution with fitness value " + notFitEnough.getFitness() + " has been removed");
}
// fill up with new
while (nests.size() < POPULATION_SIZE) {
CuckooAgent newBirdInTown = newRandomSolution();
newBirdInTown.setFitness(fitnessFunction.evaluate(newBirdInTown.getData()));
nests.add(newBirdInTown);
}
Collections.sort(nests);
fittest = nests.get(0);
System.out.println("New best solution: " + fittest.getData());
System.out.println("Fitness value: " + fittest.getFitness());
}
public FeatureVector getFittest() {
return fittest.getData();
}
}