commit cf5425987484d1f357657330f684645d8841a77b
parent ee0c1ac74664832f2b0b9da8e65ed313336adb0c
Author: Kevin Corvisier <git@kevincorvisier.fr>
Date: Sun, 1 Dec 2024 21:12:01 +0900
When an exception related to a specific card stops a game, add the card
to a file listing error-prone cards
Diffstat:
10 files changed, 227 insertions(+), 3 deletions(-)
diff --git a/src/main/java/fr/kevincorvisier/mtg/gdb/ApplicationConfig.java b/src/main/java/fr/kevincorvisier/mtg/gdb/ApplicationConfig.java
@@ -11,11 +11,15 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
import forge.game.GameFormat;
import forge.game.GameFormat.FormatSubType;
import forge.game.GameFormat.FormatType;
import forge.model.FModel;
+import fr.kevincorvisier.mtg.gdb.spring.converters.StringToCardListFileConverter;
+import fr.kevincorvisier.mtg.gdb.spring.converters.StringToFileConverter;
@Configuration
@PropertySource("application.properties")
@@ -64,4 +68,13 @@ public class ApplicationConfig
return new GameFormat("MiddleSchool", MIDDLE_SCHOOL_EFFECTIVE_DATE, MIDDLE_SCHOOL_SETS, MIDDLE_SCHOOL_BANNED_CARDS, null, false,
MIDDLE_SCHOOL_ADDITIONAL_CARDS, null, 0, FormatType.CUSTOM, FormatSubType.CUSTOM);
}
+
+ @Bean
+ public ConversionService conversionService()
+ {
+ final DefaultConversionService conversionService = new DefaultConversionService();
+ conversionService.addConverter(new StringToCardListFileConverter(conversionService));
+ conversionService.addConverter(new StringToFileConverter());
+ return conversionService;
+ }
}
diff --git a/src/main/java/fr/kevincorvisier/mtg/gdb/evaluation/GameSimulator.java b/src/main/java/fr/kevincorvisier/mtg/gdb/evaluation/GameSimulator.java
@@ -16,6 +16,7 @@ import forge.game.event.IGameEventVisitor;
import forge.game.player.RegisteredPlayer;
import fr.kevincorvisier.mtg.gdb.evaluation.GameSimulationResult.GameSimulationResultValue;
import fr.kevincorvisier.mtg.gdb.utils.TimeLimitedCodeBlock;
+import fr.kevincorvisier.mtg.gdb.validation.ErrorProneCardsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -25,6 +26,7 @@ public class GameSimulator
{
private final int gameTimeoutTurns;
private final int gameTimeoutSeconds;
+ private final ErrorProneCardsService errorProneCards;
public GameSimulationResult simulateGame(final RegisteredPlayer player, final Match match)
{
@@ -67,7 +69,10 @@ public class GameSimulator
if (e instanceof TimeoutException)
log.warn("Game stopped (timeout), result={}", result);
else
+ {
log.error("Game stopped (error), result={}", result, e);
+ errorProneCards.handleException(e);
+ }
return result;
}
diff --git a/src/main/java/fr/kevincorvisier/mtg/gdb/evaluation/GoldfishEvaluation.java b/src/main/java/fr/kevincorvisier/mtg/gdb/evaluation/GoldfishEvaluation.java
@@ -14,6 +14,7 @@ import forge.game.player.RegisteredPlayer;
import forge.player.GamePlayerUtil;
import fr.kevincorvisier.mtg.gdb.population.Individual;
import fr.kevincorvisier.mtg.gdb.population.Population;
+import fr.kevincorvisier.mtg.gdb.validation.ErrorProneCardsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -33,6 +34,8 @@ public class GoldfishEvaluation implements Evaluation
DECK_GOLDFISH.getMain().add("Island", 12);
}
+ private final ErrorProneCardsService errorProneCards;
+
@Value("${evaluation.goldfish.initial.min-games}")
private final int initialMinGames;
@Value("${evaluation.goldfish.subsequent.min-games}")
@@ -81,7 +84,7 @@ public class GoldfishEvaluation implements Evaluation
final Match mc = new Match(rules, Arrays.asList(player, opponent), "Test");
- final GameSimulator simulator = new GameSimulator(gameTimeoutTurns, gameTimeoutSeconds);
+ final GameSimulator simulator = new GameSimulator(gameTimeoutTurns, gameTimeoutSeconds, errorProneCards);
do
{
diff --git a/src/main/java/fr/kevincorvisier/mtg/gdb/evaluation/WinRatioEvaluation.java b/src/main/java/fr/kevincorvisier/mtg/gdb/evaluation/WinRatioEvaluation.java
@@ -19,6 +19,7 @@ import forge.player.GamePlayerUtil;
import fr.kevincorvisier.mtg.gdb.population.Individual;
import fr.kevincorvisier.mtg.gdb.population.Population;
import fr.kevincorvisier.mtg.gdb.utils.MagicOnlineDeckLoader;
+import fr.kevincorvisier.mtg.gdb.validation.ErrorProneCardsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -31,8 +32,9 @@ public class WinRatioEvaluation implements Evaluation
private Collection<Deck> opponents = Collections.emptySet();
private final MagicOnlineDeckLoader forgeUtils;
+ private final ErrorProneCardsService errorProneCards;
- @Value("#{'${evaluation.win-ratio.opponents-directories}'.split(',')}")
+ @Value("${evaluation.win-ratio.opponents-directories}")
private final File[] opponentsDirectories;
@Value("${evaluation.win-ratio.initial.min-games}")
private final int initialMinGames;
@@ -98,7 +100,7 @@ public class WinRatioEvaluation implements Evaluation
final Match mc = new Match(rules, Arrays.asList(player, opponent), "Test");
- final GameSimulator simulator = new GameSimulator(gameTimeoutTurns, gameTimeoutSeconds);
+ final GameSimulator simulator = new GameSimulator(gameTimeoutTurns, gameTimeoutSeconds, errorProneCards);
do
{
diff --git a/src/main/java/fr/kevincorvisier/mtg/gdb/spring/converters/StringToCardListFileConverter.java b/src/main/java/fr/kevincorvisier/mtg/gdb/spring/converters/StringToCardListFileConverter.java
@@ -0,0 +1,35 @@
+package fr.kevincorvisier.mtg.gdb.spring.converters;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.annotation.Nullable;
+import javax.validation.constraints.NotNull;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.converter.Converter;
+
+import fr.kevincorvisier.mtg.gdb.utils.CardListFile;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class StringToCardListFileConverter implements Converter<String, CardListFile>
+{
+ private final ConversionService conversionService;
+
+ @Override
+ @Nullable
+ public CardListFile convert(@NonNull @NotNull final String source)
+ {
+ try
+ {
+ final File file = conversionService.convert(source, File.class);
+ return file != null ? new CardListFile(file) : null;
+ }
+ catch (final IOException e)
+ {
+ throw new IllegalArgumentException(e);
+ }
+ }
+}
diff --git a/src/main/java/fr/kevincorvisier/mtg/gdb/spring/converters/StringToFileConverter.java b/src/main/java/fr/kevincorvisier/mtg/gdb/spring/converters/StringToFileConverter.java
@@ -0,0 +1,26 @@
+package fr.kevincorvisier.mtg.gdb.spring.converters;
+
+import java.io.File;
+
+import javax.annotation.Nullable;
+import javax.validation.constraints.NotNull;
+
+import org.springframework.beans.propertyeditors.FileEditor;
+import org.springframework.core.convert.converter.Converter;
+
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class StringToFileConverter implements Converter<String, File>
+{
+ private final FileEditor propertyEditor = new FileEditor();
+
+ @Override
+ @Nullable
+ public File convert(@NonNull @NotNull final String source)
+ {
+ propertyEditor.setAsText(source);
+ return (File) propertyEditor.getValue();
+ }
+}
diff --git a/src/main/java/fr/kevincorvisier/mtg/gdb/utils/CardListFile.java b/src/main/java/fr/kevincorvisier/mtg/gdb/utils/CardListFile.java
@@ -0,0 +1,94 @@
+package fr.kevincorvisier.mtg.gdb.utils;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+import javax.validation.constraints.NotNull;
+
+import lombok.NonNull;
+import lombok.Synchronized;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class CardListFile
+{
+ private final File file;
+
+ private boolean loaded = false;
+ private Collection<String> list;
+
+ public CardListFile(@NonNull @NotNull final File file) throws IOException
+ {
+ this.file = file;
+ load();
+ }
+
+ @Synchronized
+ public void add(final String name)
+ {
+ list.add(name);
+ log.info("add: {}", name);
+ save();
+ }
+
+ private void load() throws IOException
+ {
+ if (!file.exists() && list == null && !loaded)
+ {
+ list = new HashSet<>();
+ loaded = true;
+ return;
+ }
+
+ try (BufferedReader reader = new BufferedReader(new FileReader(file)))
+ {
+ final Collection<String> newList = new HashSet<>();
+
+ String line;
+ while ((line = reader.readLine()) != null)
+ newList.add(line.trim());
+
+ list = newList;
+ loaded = true;
+ }
+ catch (final Exception e)
+ {
+ if (list == null || !loaded)
+ throw e;
+
+ log.warn("Error while reloading file {}, keeping the previous content", file.getName(), e);
+ }
+ }
+
+ private void save()
+ {
+ final File parentFile = file.getParentFile();
+ if (parentFile != null && !parentFile.exists())
+ parentFile.mkdirs();
+
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(file)))
+ {
+ final List<String> sorted = new ArrayList<>(list);
+ Collections.sort(sorted);
+
+ for (final String card : sorted)
+ {
+ writer.write(card);
+ writer.newLine();
+ }
+ }
+ catch (final IOException e)
+ {
+ log.warn("Unable to save {}", file.getName(), e);
+ }
+ }
+}
diff --git a/src/main/java/fr/kevincorvisier/mtg/gdb/validation/ErrorProneCardsService.java b/src/main/java/fr/kevincorvisier/mtg/gdb/validation/ErrorProneCardsService.java
@@ -0,0 +1,43 @@
+package fr.kevincorvisier.mtg.gdb.validation;
+
+import java.security.InvalidParameterException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.validation.constraints.NotNull;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import fr.kevincorvisier.mtg.gdb.utils.CardListFile;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ErrorProneCardsService
+{
+ private static final Pattern PATTERN_INVALID_PARAMETER_EXCEPTION = Pattern
+ .compile("^(?<name>.+) \\(\\d+\\)'s ability resulted in no types to choose from$");
+
+ @Value("${cards.error-prone.file}")
+ private final CardListFile cards;
+
+ public void handleException(@NonNull @NotNull final Throwable e)
+ {
+ final String message = e.getMessage();
+
+ if (e instanceof InvalidParameterException)
+ {
+ final Matcher matcher = PATTERN_INVALID_PARAMETER_EXCEPTION.matcher(message);
+ if (matcher.matches())
+ cards.add(matcher.group("name"));
+ else
+ log.warn("handleException: unable to handle InvalidParameterException with message: {}", message);
+ }
+ else
+ log.warn("handleException uable to handle {}", e.getClass().getName());
+ }
+}
diff --git a/src/main/packaged-resources/cfg/application.properties b/src/main/packaged-resources/cfg/application.properties
@@ -6,5 +6,7 @@ max.no-improvement-count=20
format=MiddleSchool
card-pool=example1/card-pool.txt
+cards.error-prone.file=error-prone-cards.txt
+
validation.child.conditions=
validation.child.max-unique-cards=24
diff --git a/src/main/packaged-resources/cfg/error-prone-cards.txt b/src/main/packaged-resources/cfg/error-prone-cards.txt
@@ -0,0 +1 @@
+Shared Triumph