All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codingame.gameengine.runner.GameRunner Maven / Gradle / Ivy

package com.codingame.gameengine.runner;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.codingame.gameengine.runner.Command.InputCommand;
import com.codingame.gameengine.runner.Command.OutputCommand;
import com.codingame.gameengine.runner.dto.AgentDto;
import com.codingame.gameengine.runner.dto.GameResult;
import com.codingame.gameengine.runner.dto.Tooltip;
import com.google.gson.Gson;

abstract class GameRunner {

    static final String INTERRUPT_THREAD = "05&08#1981";
    private static final Pattern COMMAND_HEADER_PATTERN = Pattern
        .compile("\\[\\[(?.+)\\] ?(?[0-9]+)\\]");

    protected static Log log = LogFactory.getLog(GameRunner.class);
    GameResult gameResult = new GameResult();
    private ByteArrayOutputStream refereeStdout;
    private ByteArrayOutputStream refereeStderr;

    private Agent referee;
    protected final List players;
    private final List writers = new ArrayList<>();
    private final List> queues = new ArrayList<>();
    private boolean gameEnded = false;

    private String[] avatars = new String[] { "16085713250612", "16085756802960", "16085734516701", "16085746254929",
        "16085763837151", "16085720641630", "16085846089817", "16085834521247" };

    private static enum OutputResult {
        OK, TIMEOUT, TOOLONG, TOOSHORT
    };

    protected GameRunner() {
        referee = new RefereeAgent();
        players = new ArrayList();
        refereeStdout = new ByteArrayOutputStream();
        refereeStderr = new ByteArrayOutputStream();
    }

    private void initialize(Properties conf) {
        if (players.size() == 0) throw new RuntimeException("You have to add at least one player");
        if (players.size() > 8) throw new RuntimeException("You may add up to eight players only");

        referee.initialize(conf);
        gameResult.outputs.put("referee", new ArrayList<>());
        gameResult.errors.put("referee", new ArrayList<>());

        for (int i = 0; i < players.size(); i++) {
            String id = String.valueOf(i);
            Agent player = players.get(i);
            player.initialize(conf);

            List initOutputsValues = new ArrayList<>();
            initOutputsValues.add(null);
            gameResult.outputs.put(id, initOutputsValues);

            List initErrorsValues = new ArrayList<>();
            gameResult.errors.put(id, initErrorsValues);

            AgentDto agent = new AgentDto();
            agent.index = i;
            agent.agentId = player.getAgentId();
            agent.avatar = player.getAvatar() != null ? player.getAvatar()
                : "https://static.codingame.com/servlet/fileservlet?id=" + avatars[i] + "&format=viewer_avatar";
            agent.name = player.getNickname() != null ? player.getNickname() : "Player " + i;
            gameResult.agents.add(agent);
        }
    }

    private void bootstrapPlayers() {
        boolean allFailed = true;
        for (int i = 0; i < players.size(); i++) {
            Agent player = players.get(i);
            player.execute();
            allFailed = allFailed && player.isFailed();
        }

        if (allFailed) {
            throw new RuntimeException("Bootstrap of all players failed to bootsrap");
        }

        bootstrapPlayersWait();

        for (Agent agent : players) {
            BlockingQueue queue = new ArrayBlockingQueue<>(1024);
            AsynchronousWriter asyncWriter = new AsynchronousWriter(queue, agent.getInputStream());
            writers.add(asyncWriter);
            queues.add(queue);
            asyncWriter.start();
        }
    }

    private void bootstrapPlayersWait() {
        try {
            // Arbitrary time to wait for bootstrap
            Thread.sleep(300);
        } catch (InterruptedException e) {
        }
    }

    private void runAgents() {
        referee.execute();

        bootstrapPlayers();

        readInitFrameErrors();

        Command initCommand = new Command(OutputCommand.INIT);
        initCommand.addLine(players.size());

        buildInitCommand(initCommand);

        referee.sendInput(initCommand.toString());
        int round = 0;
        while (true) {
            GameTurnInfo turnInfo = readGameInfo(round);
            boolean validTurn = turnInfo.isComplete();

            gameResult.failCause = turnInfo.get(InputCommand.FAIL).orElse(null);

            if (validTurn) {
                gameResult.outputs.get("referee").add(refereeStdout.toString());
                refereeStdout.reset();
                gameResult.summaries.add(turnInfo.get(InputCommand.SUMMARY).orElse(turnInfo.get(InputCommand.INFOS).orElse(null)));
            }

            if ((validTurn) && (!turnInfo.get(InputCommand.SCORES).isPresent())) {
                NextPlayerInfo nextPlayerInfo = new NextPlayerInfo(
                    turnInfo.get(InputCommand.NEXT_PLAYER_INFO).orElse(null)
                );
                String nextPlayerOutput = getNextPlayerOutput(
                    nextPlayerInfo,
                    turnInfo.get(InputCommand.NEXT_PLAYER_INPUT).orElse(null)
                );

                for (Agent a : players) {
                    gameResult.outputs.get(String.valueOf(a.getAgentId())).add(a.getAgentId() == nextPlayerInfo.nextPlayer ? nextPlayerOutput : null);
                }

                if (nextPlayerOutput != null) {
                    log.info("\t=== Read from player");
                    log.info(nextPlayerOutput);
                    log.info("\t=== End Player");
                    sendPlayerOutput(nextPlayerOutput, nextPlayerInfo.nbLinesNextOutput);
                } else {
                    sendTimeOut();
                }
            }

            readError(referee);
            if (!validTurn) {
                gameResult.views.add(null);
            } else {
                gameResult.views.add(turnInfo.get(InputCommand.VIEW).orElse(null));

                turnInfo.get(InputCommand.UINPUT).ifPresent(line -> {
                    gameResult.uinput.add(line);
                });

                turnInfo.get(InputCommand.METADATA).ifPresent(line -> {
                    gameResult.metadata = line;
                });

                final int currentRound = round;
                turnInfo.get(InputCommand.TOOLTIP).ifPresent(line -> {
                    String[] tooltipData = line.split("\n");
                    for (int i = 0; i < tooltipData.length / 2; ++i) {
                        String text = tooltipData[i * 2];
                        int eventId = Integer.valueOf(tooltipData[i * 2 + 1]);
                        gameResult.tooltips.add(new Tooltip(text, eventId, currentRound));
                    }
                });

                turnInfo.get(InputCommand.SCORES).ifPresent(scores -> {
                    for (String line : scores.split("\n")) {
                        String[] parts = line.split(" ");
                        if (parts.length > 1) {
                            int player = Integer.decode(parts[0]);
                            int score = Integer.decode(parts[1]);
                            gameResult.scores.put(player, score);
                        }
                    }
                });
            }
            round++;
            if (!validTurn || turnInfo.isEndTurn()) {
                break;
            }
        }

        for (BlockingQueue queue : queues) {
            queue.offer(INTERRUPT_THREAD);
        }

    }

    abstract protected void buildInitCommand(Command initCommand);

    private String getJSONResult() {
        addPlayerIds();

        return new Gson().toJson(gameResult);
    }

    private void addPlayerIds() {
        for (int i = 0; i < players.size(); i++) {
            gameResult.ids.put(i, players.get(i).getAgentId());
        }
    }

    /**
     * Read all output from standard error stream
     */
    private void readInitFrameErrors() {
        for (int i = 0; i < players.size(); i++) {
            Agent player = players.get(i);
            String id = String.valueOf(i);
            gameResult.errors.get(id).add(player.readError());
        }
        readError(referee);
    }

    /**
     * Read all output from standard error stream
     */
    private void readError(Agent agent) {
        if (agent == referee) {
            gameResult.errors.get("referee").add(refereeStderr.toString());
            refereeStderr.reset();
        } else {
            for (Agent a : players) {
                gameResult.errors.get(String.valueOf(a.getAgentId())).add(a == agent ? agent.readError() : null);
            }
        }
    }

    private void sendPlayerOutput(String output, int nbLines) {
        Command command = new Command(OutputCommand.SET_PLAYER_OUTPUT, output.split("(\\n|\\r\\n)"));
        referee.sendInput(command.toString());
    }

    private void sendTimeOut() {
        Command command = new Command(OutputCommand.SET_PLAYER_TIMEOUT);
        referee.sendInput(command.toString());
    }

    private String getNextPlayerOutput(NextPlayerInfo nextPlayerInfo, String nextPlayerInput) {
        Agent player = players.get(nextPlayerInfo.nextPlayer);

        // Send player input to input queue
        queues.get(nextPlayerInfo.nextPlayer).offer(nextPlayerInput);

        // Wait for player output then read error
        String playerOutput = player.getOutput(nextPlayerInfo.nbLinesNextOutput, nextPlayerInfo.timeout);
        if (playerOutput != null)
            playerOutput = playerOutput.replace('\r', '\n');
        readError(player);

        if (checkOutput(playerOutput, nextPlayerInfo.nbLinesNextOutput) != OutputResult.OK) {
            return null;
        }
        if ((playerOutput != null) && playerOutput.isEmpty() && (nextPlayerInfo.nbLinesNextOutput == 1)) {
            return "\n";
        }
        if ((playerOutput != null) && (playerOutput.length() > 0)
            && (playerOutput.charAt(playerOutput.length() - 1) != '\n')) {
            return playerOutput + '\n';
        }
        return playerOutput;
    }

    private GameTurnInfo readGameInfo(int round) {
        GameTurnInfo turnInfo = new GameTurnInfo();

        referee.sendInput(new Command(OutputCommand.GET_GAME_INFO).toString());

        while (!turnInfo.isComplete() && !turnInfo.refereeHasFailed()) {
            Command command = readCommand(referee, round);
            if (command == null) {
                return turnInfo;
            }
            turnInfo.put(command);
        }
        return turnInfo;
    }

    private Command readCommand(Agent agent, int round) {
        try {
            String output = agent.getOutput(1, 150_000);
            if (output != null) {
                output = output.replace('\r', '\n');
            }
            if (checkOutput(output, 1) != OutputResult.OK) {
                throw new RuntimeException("Invalid Referee command: " + output);
            }

            Matcher m = COMMAND_HEADER_PATTERN.matcher(output.trim());
            if (m.matches()) {
                String command = m.group("cmd");
                int nbLinesToRead = Integer.parseInt(m.group("lineCount"));

                if (nbLinesToRead >= 0) {
                    output = agent.getOutput(nbLinesToRead, 150_000, round == 0);
                    output = output.replace('\r', '\n');
                } else {
                    throw new RuntimeException("Invalid Referee command line count: " + output);
                }
                if (checkOutput(output, nbLinesToRead) != OutputResult.OK) {
                    throw new RuntimeException(
                        "Error reading Referee command. Buffer capacity: " + output.length() + " / "
                            + (round == 0 ? RefereeAgent.REFEREE_MAX_BUFFER_SIZE_EXTRA : RefereeAgent.REFEREE_MAX_BUFFER_SIZE)
                    );
                }
                return new Command(InputCommand.valueOf(command), output);
            } else {
                throw new RuntimeException("Invalid referee command: " + output);
            }
        } catch (RuntimeException err) {
            err.printStackTrace();
            return new Command(InputCommand.FAIL, err.toString());
        }
    }

    private OutputResult checkOutput(String output, int nbExpectedLines) {
        if ((output == null) || (output.isEmpty())) {
            if (nbExpectedLines <= 0) {
                return OutputResult.OK;
            } else {
                return OutputResult.TIMEOUT;
            }
        }

        int nbOccurences = 0;
        for (int i = 0; i < output.length(); ++i) {
            if (output.charAt(i) == '\n') {
                ++nbOccurences;
            }
        }

        if (nbOccurences < nbExpectedLines) {
            return OutputResult.TOOSHORT;
        }
        if (nbOccurences > nbExpectedLines) {
            return OutputResult.TOOLONG;
        }
        return OutputResult.OK;
    }

    /**
     * Runs the game and attempts to start a server on the port 8888.
     * 

* Open a webpage to the server to watch the game's replay. */ public void start() { start(8888); } /** * Runs the game and attempts to start a server on the given port. *

* Open a webpage to the server to watch the game's replay. * * @param port * the port on which to attempt to start the a server for the game's replay. */ public void start(int port) { runGame(); new Renderer(port).render(players.size(), getJSONResult()); } /** * Runs the game without a server and returns computed game results * * @return game result of the game */ public GameResult simulate() { runGame(); addPlayerIds(); return gameResult; } private void requireGameNotEnded() { if (gameEnded) { throw new RuntimeException("This game has ended"); } } /** * Simulates the game and gathers game results */ private void runGame() { PrintStream out = System.out; System.setOut(new PrintStream(new OutputStream() { @Override public void write(int b) throws IOException { out.write(b); refereeStdout.write(b); } })); PrintStream err = System.err; System.setErr(new PrintStream(new OutputStream() { @Override public void write(int b) throws IOException { err.write(b); refereeStderr.write(b); } })); requireGameNotEnded(); Properties conf = new Properties(); initialize(conf); runAgents(); referee.destroy(); destroyPlayers(); gameEnded = true; System.setOut(out); System.setErr(err); } /** * Destroys all players */ private void destroyPlayers() { for (Agent player : players) { player.destroy(); } } static class NextPlayerInfo { int nextPlayer; int nbLinesNextOutput; long timeout; NextPlayerInfo(String command) { String[] nextPlayerInfo = command.split("\n"); nextPlayer = Integer.decode(nextPlayerInfo[0]); nbLinesNextOutput = Integer.decode(nextPlayerInfo[1]); timeout = Long.decode(nextPlayerInfo[2]); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy