mtg-genetic-deckbuilding

Generating and improving Magic: The Gathering decks using a genetic algorithm
git clone https://kevincorvisier.fr/git/mtg-genetic-deckbuilding.git
Log | Files | Refs | LICENSE

commit 0fa17222c7a0370dc2ea87aea1db8d625735ea55
parent 62f5f0a3fdec531acab281180a79e9f0fa9add89
Author: Kevin Corvisier <git@kevincorvisier.fr>
Date:   Thu, 19 Dec 2024 12:31:51 +0900

Make Crossover.crossover return a Pair instead of a List, move crossover rate to NextGenerationService

Diffstat:
Msrc/main/java/fr/kevincorvisier/mtg/gdb/ApplicationConfig.java | 1-
Msrc/main/java/fr/kevincorvisier/mtg/gdb/operators/crossover/Crossover.java | 4++--
Msrc/main/java/fr/kevincorvisier/mtg/gdb/operators/crossover/UniformCrossover.java | 50++++++++++++++++++--------------------------------
Msrc/main/java/fr/kevincorvisier/mtg/gdb/population/NextGenerationService.java | 22++++++++++++++++------
Msrc/main/packaged-resources/cfg/application.properties | 7+++++++
Dsrc/main/packaged-resources/cfg/crossover.properties | 1-
6 files changed, 43 insertions(+), 42 deletions(-)

diff --git a/src/main/java/fr/kevincorvisier/mtg/gdb/ApplicationConfig.java b/src/main/java/fr/kevincorvisier/mtg/gdb/ApplicationConfig.java @@ -23,7 +23,6 @@ import fr.kevincorvisier.mtg.gdb.spring.converters.StringToFileConverter; @Configuration @PropertySource("application.properties") -@PropertySource("crossover.properties") @PropertySource("evaluation.properties") @PropertySource("mutation.properties") @PropertySource("population.properties") diff --git a/src/main/java/fr/kevincorvisier/mtg/gdb/operators/crossover/Crossover.java b/src/main/java/fr/kevincorvisier/mtg/gdb/operators/crossover/Crossover.java @@ -1,12 +1,12 @@ package fr.kevincorvisier.mtg.gdb.operators.crossover; -import java.util.List; +import org.apache.commons.math3.util.Pair; import fr.kevincorvisier.mtg.gdb.population.Individual; public interface Crossover { - List<Individual> crossover(final Individual parent1, final Individual parent2); + Pair<Individual, Individual> crossover(final Individual parent1, final Individual parent2); void setGenerationCount(final int generationCount); } diff --git a/src/main/java/fr/kevincorvisier/mtg/gdb/operators/crossover/UniformCrossover.java b/src/main/java/fr/kevincorvisier/mtg/gdb/operators/crossover/UniformCrossover.java @@ -3,7 +3,7 @@ package fr.kevincorvisier.mtg.gdb.operators.crossover; import java.util.List; import java.util.Random; -import org.springframework.beans.factory.annotation.Value; +import org.apache.commons.math3.util.Pair; import org.springframework.stereotype.Service; import forge.deck.CardPool; @@ -24,47 +24,33 @@ public class UniformCrossover implements Crossover protected int generationCount = 0; private int individualIncrementCounter = 0; - @Value("${crossover.no-crossover-chance}") - private final double noCrossoverChance; - @Override - public List<Individual> crossover(final Individual parent1, final Individual parent2) + public Pair<Individual, Individual> crossover(final Individual parent1, final Individual parent2) { - final Deck first; - final Deck second; + final Deck first = new Deck(); + final Deck second = new Deck(); - if (rnd.nextDouble() <= noCrossoverChance) - { - first = new Deck(parent1.getDeck()); - second = new Deck(parent2.getDeck()); - } - else + for (final DeckSection section : SECTIONS) { - first = new Deck(); - second = new Deck(); + final List<PaperCard> list1 = parent1.getDeck().getOrCreate(section).toFlatList(); + final List<PaperCard> list2 = parent2.getDeck().getOrCreate(section).toFlatList(); + final int size = Math.max(list1.size(), list2.size()); - for (final DeckSection section : SECTIONS) - { - final List<PaperCard> list1 = parent1.getDeck().getOrCreate(section).toFlatList(); - final List<PaperCard> list2 = parent2.getDeck().getOrCreate(section).toFlatList(); - final int size = Math.max(list1.size(), list2.size()); - - final CardPool child1Section = first.getOrCreate(section); - final CardPool child2Section = second.getOrCreate(section); + final CardPool child1Section = first.getOrCreate(section); + final CardPool child2Section = second.getOrCreate(section); - for (int i = 0; i != size; i++) - { - final PaperCard card1 = i < list1.size() ? list1.get(i) : null; - final PaperCard card2 = i < list2.size() ? list2.get(i) : null; + for (int i = 0; i != size; i++) + { + final PaperCard card1 = i < list1.size() ? list1.get(i) : null; + final PaperCard card2 = i < list2.size() ? list2.get(i) : null; - final boolean firstCardToFirstChild = rnd.nextBoolean(); - child1Section.add(firstCardToFirstChild ? card1 : card2); - child2Section.add(firstCardToFirstChild ? card2 : card1); - } + final boolean firstCardToFirstChild = rnd.nextBoolean(); + child1Section.add(firstCardToFirstChild ? card1 : card2); + child2Section.add(firstCardToFirstChild ? card2 : card1); } } - return List.of(new Individual(uniqName(), first), new Individual(uniqName(), second)); + return Pair.create(new Individual(uniqName(), first), new Individual(uniqName(), second)); } private String uniqName() diff --git a/src/main/java/fr/kevincorvisier/mtg/gdb/population/NextGenerationService.java b/src/main/java/fr/kevincorvisier/mtg/gdb/population/NextGenerationService.java @@ -2,6 +2,7 @@ package fr.kevincorvisier.mtg.gdb.population; import java.util.Collection; import java.util.HashSet; +import java.util.Random; import org.apache.commons.math3.util.Pair; import org.springframework.beans.factory.annotation.Value; @@ -29,6 +30,8 @@ public class NextGenerationService private final float newRate; @Value("${population.maximum-size}") private final int maximumSize; + @Value("${crossover.rate}") + private final double crossoverRate; private final Crossover crossover; private final Mutation mutation; @@ -37,6 +40,8 @@ public class NextGenerationService private final RandomDeckGenerator deckGenerator; private final IndividualValidationService validationService; + private final Random rnd = new Random(); + public Population toNextGeneration(final Population population) { final DefaultPopulation next = new DefaultPopulation(); @@ -79,20 +84,25 @@ public class NextGenerationService while (next.getSize() < maximumSize) { // Selection - final Pair<Individual, Individual> parents = selection.select(population); - final double estimatedFitness = (parents.getFirst().getFitness() + parents.getSecond().getFitness()) / 2d; + Pair<Individual, Individual> pair = selection.select(population); + final double estimatedFitness = (pair.getFirst().getFitness() + pair.getSecond().getFitness()) / 2d; // Crossover - for (final Individual child : crossover.crossover(parents.getFirst(), parents.getSecond())) + if (rnd.nextDouble() < crossoverRate) + { + pair = crossover.crossover(pair.getFirst(), pair.getSecond()); + } + + for (final Individual individual : new Individual[] { pair.getFirst(), pair.getSecond() }) { if (result.size() >= maximumSize) continue; // Ensure second child will not breach the population limit // Mutation - mutation.mutate(child, estimatedFitness); + mutation.mutate(individual, estimatedFitness); - if (validationService.validate(child, next)) - next.addIndividual(child); + if (validationService.validate(individual, next)) + next.addIndividual(individual); } } } diff --git a/src/main/packaged-resources/cfg/application.properties b/src/main/packaged-resources/cfg/application.properties @@ -10,3 +10,10 @@ cards.error-prone.file=error-prone-cards.txt validation.child.conditions= validation.child.max-unique-cards=24 + + +# +# Crossover +# + +crossover.rate=0.8 diff --git a/src/main/packaged-resources/cfg/crossover.properties b/src/main/packaged-resources/cfg/crossover.properties @@ -1 +0,0 @@ -crossover.no-crossover-chance=0.333