Skip to content

Content of file IterationalGame.java

/*
 * Copyright © 2020 Fachhochschule für die Wirtschaft (FHDW) Hannover
 *
 * This file is part of gaming-contest.
 *
 * Gaming-contest is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * Gaming-contest is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * gaming-contest. If not, see <http://www.gnu.org/licenses/>.
 */
package de.fhdw.gaming.ipspiel21.iterationalContest;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.stream.Collectors;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Options;

import de.fhdw.gaming.contest.RecordingObserver;
import de.fhdw.gaming.contest.ui.InteractiveStreamInputProvider;
import de.fhdw.gaming.contest.util.CombinatoricsHelper;
import de.fhdw.gaming.contest.util.ComparablePair;
import de.fhdw.gaming.core.domain.DefaultGameBuilderFactoryProvider;
import de.fhdw.gaming.core.domain.Game;
import de.fhdw.gaming.core.domain.GameBuilder;
import de.fhdw.gaming.core.domain.GameBuilderFactory;
import de.fhdw.gaming.core.domain.GameException;
import de.fhdw.gaming.core.domain.Player;
import de.fhdw.gaming.core.domain.PlayerState;
import de.fhdw.gaming.core.domain.Strategy;
import de.fhdw.gaming.core.domain.util.GameBuilderFactoryWrapper;
import de.fhdw.gaming.core.ui.InputProvider;
import de.fhdw.gaming.core.ui.InputProviderException;
import de.fhdw.gaming.core.ui.type.validator.MaxValueValidator;
import de.fhdw.gaming.core.ui.type.validator.MinValueValidator;
import de.fhdw.gaming.core.ui.util.ChainedInputProvider;
import de.fhdw.gaming.core.ui.util.NonInteractiveInputProvider;
import de.fhdw.gaming.ipspiel21.evolution.EvolutionPlayer;
import de.fhdw.gaming.ipspiel21.evolution.GameHistoryCollection;
import de.fhdw.gaming.ipspiel21.evolution.MemoryObserver;

/**
 * Provides the main entry point.
 */
public final class IterationalGame {

    /**
     * Type of game.
     */
    private static final String PARAM_GAME = "iterationalGame";
    /**
     * The number of games to be played.
     */
    private static final String PARAM_REPEAT_COUNT = "repeatCount";
    /**
     * If non-null, the events of all contests are recorded into this directory.
     */
    private final File recordingDirectory;

    /**
     * Constructor.
     *
     * @param recordingDirectory If non-null, the events of all contests are recorded into this directory.
     */
    public IterationalGame(final String recordingDirectory) {
        this.recordingDirectory = recordingDirectory != null ? new File(recordingDirectory) : null;
    }

    /**
     * Runs the contest.
     */
    public void run() throws InputProviderException, GameException, InterruptedException, IOException {
        final CompetitionParameters competitionParameters = this.determineCompetitionParameters();
        final CompetitionResults competitionResults = this.runCompetition(competitionParameters);

        System.out.println();

        final int numberOfGamesPlayed = competitionResults.getTotalNumberOfGamesPlayed();
        System.out.println(String.format("A total of %d games played. Results:", numberOfGamesPlayed));

        this.printResult(numberOfGamesPlayed, competitionResults.getGameResults());
    }

    /**
     * Prints the result of the competition.
     *
     * @param numberOfGamesPlayed The number of games played.
     * @param gameResults         The game results.
     */
    private void printResult(final int numberOfGamesPlayed, final Map<Class<?>, StrategyData> gameResults) {
        final SortedMap<ComparablePair<Integer, Integer>, List<StrategyData>> ranking = new TreeMap<>();
        final Comparator<Integer> reverseCompare = ((Comparator<Integer>) Integer::compareTo).reversed();

        int maxNameLength = 0;
        int maxOutcomeLength = 0;
        for (final StrategyData strategyData : gameResults.values()) {
            final String name = strategyData.getName();
            // first order by games won, then (giving equality) by games that ended in a draw
            final ComparablePair<Integer,
                    Integer> key = ComparablePair.of(
                            strategyData.getCounters().getOrDefault(PlayerState.WON, 0),
                            reverseCompare,
                            strategyData.getCounters().getOrDefault(PlayerState.DRAW, 0),
                            reverseCompare);
            ranking.computeIfAbsent(key, (final var k) -> new ArrayList<>()).add(strategyData);
            maxNameLength = Math.max(maxNameLength, name.length());
            maxOutcomeLength = Math
                    .max(maxOutcomeLength, String.format("%.3f", strategyData.getTotalOutcome()).length());
        }

        final int maxNumFieldWidth = String.valueOf(numberOfGamesPlayed).length();

        int place = 1;
        for (final Map.Entry<ComparablePair<Integer, Integer>, List<StrategyData>> rankingEntry : ranking.entrySet()) {
            final List<StrategyData> strategyDataList = rankingEntry.getValue();
            boolean first = true;
            for (final StrategyData strategyData : strategyDataList) {
                System.out.println(
                        String.format(
                                String.format(
                                        "  %%s Strategy %%-%1$ds "
                                                + "[WON: %%%2$dd, DRAW: %%%2$dd, LOST: %%%2$dd, RESIGNED: %%%2$dd] "
                                                + "[OUTCOME: %%%3$d.3f]",
                                        maxNameLength,
                                        maxNumFieldWidth,
                                        maxOutcomeLength),

                                String.format(first ? "%4d. place:" : "            ", place),
                                strategyData.getName(),
                                rankingEntry.getKey().getFirst(),
                                rankingEntry.getKey().getSecond(),
                                strategyData.getCounters().getOrDefault(PlayerState.LOST, 0),
                                strategyData.getCounters().getOrDefault(PlayerState.RESIGNED, 0),
                                strategyData.getTotalOutcome()));
                first = false;
            }
            place += strategyDataList.size();
        }
    }

    /**
     * Determines the competition parameters.
     */
    private CompetitionParameters determineCompetitionParameters() throws InputProviderException, GameException {
        final Set<GameBuilderFactory> gameBuilderFactories = new LinkedHashSet<>();
        for (final GameBuilderFactory gameBuilderFactory : new DefaultGameBuilderFactoryProvider()
                .getGameBuilderFactories()) {
            gameBuilderFactories.add(new GameBuilderFactoryWrapper(gameBuilderFactory));
        }

        final InputProvider inputProvider = new InteractiveStreamInputProvider(System.in, System.out)
                .needObject(IterationalGame.PARAM_GAME, "Which game to play", Optional.empty(), gameBuilderFactories)
                .needInteger(
                        IterationalGame.PARAM_REPEAT_COUNT,
                        "Number of games per contest",
                        Optional.of(100),
                        new MinValueValidator<>(1),
                        new MaxValueValidator<>(1000))
                .needInteger(
                        GameBuilderFactory.PARAM_MAX_COMPUTATION_TIME_PER_MOVE,
                        "Maximum computation time in seconds per player and move",
                        Optional.of(GameBuilder.DEFAULT_MAX_COMPUTATION_TIME_PER_MOVE),
                        new MinValueValidator<>(1));

        if (gameBuilderFactories.size() == 1) {
            inputProvider.fixedObject(IterationalGame.PARAM_GAME, gameBuilderFactories.iterator().next());
        }
        final Map<String, Object> data = inputProvider.requestData("Competition parameters");

        final GameBuilderFactory gameBuilderFactory = (GameBuilderFactory) data.get(IterationalGame.PARAM_GAME);
        final int repeatCount = (Integer) data.get(IterationalGame.PARAM_REPEAT_COUNT);
        final int maxComputationTimePerMove = (Integer) data
                .get(GameBuilderFactory.PARAM_MAX_COMPUTATION_TIME_PER_MOVE);
        return new CompetitionParameters(gameBuilderFactory, repeatCount, maxComputationTimePerMove);
    }

    /**
     * Runs a competition.
     *
     * @param competitionParameters The competition parameters.
     * @return A map where to store how the strategies have performed in the competition.
     */
    private CompetitionResults runCompetition(final CompetitionParameters competitionParameters)
            throws InputProviderException, GameException, InterruptedException, IOException {

        final CompetitionResults competitionResults = new CompetitionResults();

        final GameBuilderFactory gameBuilderFactory = competitionParameters.getGameBuilderFactory();
        final List<Strategy<?, ?, ?>> strategies = new ArrayList<>(gameBuilderFactory.getStrategies());
        // a competition must run non-interactively, so we have to filter out interactive strategies
        strategies.removeIf(Strategy::isInteractive);

        final Comparator<Strategy<?, ?, ?>> comp = (final Strategy<?, ?, ?> l, final Strategy<?, ?, ?> r) -> Integer
                .compare(strategies.indexOf(l), strategies.indexOf(r));

        int contestNumber = 1;
        for (int numPlayers = gameBuilderFactory.getMinimumNumberOfPlayers();
                numPlayers <= gameBuilderFactory.getMaximumNumberOfPlayers(); ++numPlayers) {

            for (final List<Strategy<?, ?, ?>> strategySet : CombinatoricsHelper
                    .combinationsWithRepetition(strategies, numPlayers)
                    .flatMap(
                            (final List<Strategy<?, ?, ?>> strategyList) -> CombinatoricsHelper
                                    .permutationsWithRepetition(strategyList, comp))
                    .collect(Collectors.toList())) {

                System.out.println(
                        String.format(
                                "Contest %d. Number of players: %d. Strategies involved: %s",
                                contestNumber,
                                numPlayers,
                                strategySet));

                final ContestParameters contestParameters = this
                        .determineContestParameters(competitionParameters, strategySet);
                this.runContest(contestNumber, contestParameters, competitionResults);

                ++contestNumber;
            }
        }

        return competitionResults;
    }

    /**
     * Determines the contest parameters.
     *
     * @param competitionParameters The competition parameters.
     * @param strategies            A set of strategies to be used for this contest.
     */
    private ContestParameters determineContestParameters(final CompetitionParameters competitionParameters,
            final List<? extends Strategy<?, ?, ?>> strategies) throws InputProviderException, GameException {

        final ListIterator<? extends Strategy<?, ?, ?>> itStrategy = strategies.listIterator(strategies.size());

        InputProvider mostRecentProvider = null;
        int nextPlayerId = strategies.size();
        while (itStrategy.hasPrevious()) {
            final InputProvider lastPlayerProvider = mostRecentProvider;
            final InputProvider playerInputProvider = lastPlayerProvider == null ? new NonInteractiveInputProvider()
                    : new ChainedInputProvider(
                            new NonInteractiveInputProvider(),
                            (final Map<String, Object> lastDataSet) -> lastPlayerProvider);

            playerInputProvider.fixedString(GameBuilderFactory.PARAM_PLAYER_NAME, Integer.toString(nextPlayerId--));
            playerInputProvider.fixedObject(GameBuilderFactory.PARAM_PLAYER_STRATEGY, itStrategy.previous());
            mostRecentProvider = playerInputProvider;
        }

        final InputProvider firstPlayerProvider = mostRecentProvider;
        final InputProvider gameInputProvider = firstPlayerProvider == null ? new NonInteractiveInputProvider()
                : new ChainedInputProvider(
                        new NonInteractiveInputProvider(),
                        (final Map<String, Object> lastDataSet) -> firstPlayerProvider);
        gameInputProvider.fixedInteger(
                GameBuilderFactory.PARAM_MAX_COMPUTATION_TIME_PER_MOVE,
                competitionParameters.getMaxComputationTimePerMove());

        final GameBuilder gameBuilder = competitionParameters.getGameBuilderFactory()
                .createGameBuilder(gameInputProvider);
        return new ContestParameters(gameBuilder, competitionParameters.getNumberOfGamesPerContest());
    }

    /**
     * Runs a contest.
     *
     * @param contestNumber      The number of the contest.
     * @param contestParameters  The contest parameters.
     * @param competitionResults The competition results where to store how the strategies have performed in the
     *                           competition.
     */
    private void runContest(final int contestNumber, final ContestParameters contestParameters,
            final CompetitionResults competitionResults) throws GameException, InterruptedException, IOException {

        final File contestDirectory;
        if (this.recordingDirectory != null) {
            contestDirectory = new File(this.recordingDirectory, String.format("%d", contestNumber));
        } else {
            contestDirectory = null;
        }

        final Map<Class<?>, StrategyData> gameResults = competitionResults.getGameResults();

        final GameBuilder gameBuilder = contestParameters.getGameBuilder();

        Optional<GameHistoryCollection> memoryFirstPlayer = Optional.empty();
        Optional<GameHistoryCollection> memorySecondPlayer = Optional.empty();

        for (int i = 1; i <= contestParameters.getNumberOfGames(); ++i) {
            RecordingObserver observer = null;
            final MemoryObserver memobserver = new MemoryObserver();
            try (Game<?, ?, ?, ?> game = gameBuilder.build(i)) {
                if (contestDirectory != null) {
                    final File gameEventFile = createGameEventFile(contestDirectory, String.format("%d.log", i));
                    observer = new RecordingObserver(gameEventFile);
                    game.addObserver(observer);

                }
                game.addObserver(memobserver);
                final List<Player> players = new ArrayList<>(game.getState().getPlayers().values());
                this.setMemoryToPlayer(players.get(0), memoryFirstPlayer);
                this.setMemoryToPlayer(players.get(1), memorySecondPlayer);

                game.start();

                while (!game.isFinished()) {
                    game.makeMove();
                }

                memoryFirstPlayer = this.getMemoryFromPlayer(players.get(0));
                memorySecondPlayer = this.getMemoryFromPlayer(players.get(1));

                competitionResults.incrementTotalNumberOfGamesPlayed();
                System.out.print('.');

                final Map<String, ? extends Strategy<?, ?, ?>> strategies = game.getStrategies();
                for (final Player player : game.getPlayers().values()) {
                    final Strategy<?, ?, ?> strategy = strategies.get(player.getName());
                    final Class<?> strategyClass = strategy.getClass();

                    final StrategyData strategyData = gameResults.computeIfAbsent(
                            strategyClass,
                            (final var k) -> new StrategyData(strategy.toString(), new EnumMap<>(PlayerState.class)));
                    strategyData.getCounters().compute(
                            player.getState(),
                            (final PlayerState key, final Integer value) -> value == null ? 1 : value + 1);
                    strategyData.addOutcome(player.getOutcome().orElseThrow());
                }
            } finally {
                if (observer != null) {
                    observer.close();
                }
            }
Consider using a try-with-resources statement instead of explicitly closing the resource.
Java 7 introduced the try-with-resources statement. This statement ensures that each resource is closed at the end of the statement. It avoids the need of explicitly closing the resources in a finally block. Additionally exceptions are better handled: If an exception occurred both in the `try` block and `finally` block, then the exception from the try block was suppressed. With the `try`-with-resources statement, the exception thrown from the try-block is preserved.
    
        

public class TryWithResources {
    public void run() {
        InputStream in = null;
        try {
            in = openInputStream();
            int i = in.read();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) in.close();
            } catch (IOException ignored) {
                // ignored
            }
        }

        // better use try-with-resources
        try (InputStream in2 = openInputStream()) {
            int i = in2.read();
        }
    }
}

        
    
See PMD documentation.
} System.out.println(); } /** * Returns the memory of the provided player. * * @param player * @return The memory of the given player */ Optional<GameHistoryCollection> getMemoryFromPlayer(final Player player) { if (player instanceof EvolutionPlayer) { final EvolutionPlayer ev = (EvolutionPlayer) player; return Optional.of(ev.getGameHistoryCollection()); } else { return Optional.empty(); } } /** * Sets the provided memory to the provided player. * * @param player * @param memory */ void setMemoryToPlayer(final Player player, final Optional<GameHistoryCollection> memory) { if (memory.isPresent()) { final EvolutionPlayer ev = (EvolutionPlayer) player; ev.setGameHistoryCollection(memory.get()); } } /** * Returns a {@link File} object for storing game events. * * @param parentDirectory The parent directory. May be {@code null} if game events shall not be saved. * @param fileName The file name. * @return An appropriate {@link File} object or {@code null} if game events shall not be saved. * @throws IOException if creating the parent directory (if not present yet) has failed. */ private static File createGameEventFile(final File parentDirectory, final String fileName) throws IOException { if (parentDirectory == null) { return null; } if (!parentDirectory.exists() && !parentDirectory.mkdirs()) { throw new IOException( String.format( "Could not create directory for recording events: %s", parentDirectory.getCanonicalPath())); } if (!parentDirectory.isDirectory()) { throw new IOException( String.format( "Directory for recording events is not a directory: %s", parentDirectory.getCanonicalPath())); } return new File(parentDirectory, fileName); } /** * The main entry point. * * @param parameters The parameters passed to the application. */ public static void main(final String[] parameters) { final Options options = new Options(); options.addOption("r", "recordDir", true, "Specifies the directory where to record events"); try { final CommandLine commandLine = new DefaultParser().parse(options, parameters); new IterationalGame(commandLine.getOptionValue('r')).run(); } catch (final Exception e) { System.err.print("Game contest aborted due to exception: "); e.printStackTrace(System.err); System.exit(3); } } }