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

org.ggp.base.util.match.Match Maven / Gradle / Ivy

There is a newer version: 0.0.15
Show newest version
package org.ggp.base.util.match;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

import org.ggp.base.util.crypto.BaseCryptography.EncodedKeyPair;
import org.ggp.base.util.crypto.SignableJSON;
import org.ggp.base.util.game.Game;
import org.ggp.base.util.game.RemoteGameRepository;
import org.ggp.base.util.gdl.factory.GdlFactory;
import org.ggp.base.util.gdl.factory.exceptions.GdlFormatException;
import org.ggp.base.util.gdl.grammar.Gdl;
import org.ggp.base.util.gdl.grammar.GdlConstant;
import org.ggp.base.util.gdl.grammar.GdlFunction;
import org.ggp.base.util.gdl.grammar.GdlRelation;
import org.ggp.base.util.gdl.grammar.GdlSentence;
import org.ggp.base.util.gdl.grammar.GdlTerm;
import org.ggp.base.util.gdl.scrambler.GdlScrambler;
import org.ggp.base.util.gdl.scrambler.MappingGdlScrambler;
import org.ggp.base.util.gdl.scrambler.NoOpGdlScrambler;
import org.ggp.base.util.statemachine.Move;
import org.ggp.base.util.statemachine.Role;
import org.ggp.base.util.symbol.factory.SymbolFactory;
import org.ggp.base.util.symbol.factory.exceptions.SymbolFormatException;
import org.ggp.base.util.symbol.grammar.SymbolList;

import external.JSON.JSONArray;
import external.JSON.JSONException;
import external.JSON.JSONObject;

/**
 * Match encapsulates all of the information relating to a single match.
 * A match is a single play through a game, with a complete history that
 * lists what move each player made at each step through the match. This
 * also includes other relevant metadata about the match, including some
 * unique identifiers, configuration information, and so on.
 *
 * NOTE: Match objects created by a player, representing state read from
 * a server, are not completely filled out. For example, they only get an
 * ephemeral Game object, which has a rulesheet but no key or metadata.
 * Gamers which do not derive from StateMachineGamer also do not keep any
 * information on what states have been observed, because (somehow) they
 * are representing games without using state machines. In general, these
 * player-created Match objects shouldn't be sent out into the ecosystem.
 *
 * @author Sam
 */
public final class Match
{
    private final String matchId;
    private final String randomToken;
    private final String spectatorAuthToken;
    private final String tournamentNameFromHost;
    private final int playClock;
    private final int startClock;
    private final int previewClock;
    private final Date startTime;
    private final Game theGame;
    private final List> moveHistory;
    private final List> stateHistory;
    private final List> errorHistory;
    private final List stateTimeHistory;
    private boolean isCompleted;
    private boolean isAborted;
    private final List goalValues;
    private final int numRoles;

    private EncodedKeyPair theCryptographicKeys;
    private List thePlayerNamesFromHost;
    private List isPlayerHuman;

    private GdlScrambler theGdlScrambler = new NoOpGdlScrambler();

    public Match(String matchId, int previewClock, int startClock, int playClock, Game theGame, String tournamentNameFromHost)
    {
        this.matchId = matchId;
        this.tournamentNameFromHost = tournamentNameFromHost;
        this.previewClock = previewClock;
        this.startClock = startClock;
        this.playClock = playClock;
        this.theGame = theGame;

        this.startTime = new Date();
        this.randomToken = getRandomString(32);
        this.spectatorAuthToken = getRandomString(12);
        this.isCompleted = false;
        this.isAborted = false;

        this.numRoles = Role.computeRoles(theGame.getRules()).size();

        this.moveHistory = new ArrayList>();
        this.stateHistory = new ArrayList>();
        this.stateTimeHistory = new ArrayList();
        this.errorHistory = new ArrayList>();

        this.goalValues = new ArrayList();
    }

    public Match(String theJSON, Game theGame, String authToken) throws JSONException, SymbolFormatException, GdlFormatException {
        JSONObject theMatchObject = new JSONObject(theJSON);

        this.matchId = theMatchObject.getString("matchId");
        this.startClock = theMatchObject.getInt("startClock");
        this.playClock = theMatchObject.getInt("playClock");
        if (theGame == null) {
            this.theGame = RemoteGameRepository.loadSingleGame(theMatchObject.getString("gameMetaURL"));
            if (this.theGame == null) {
                throw new RuntimeException("Could not find metadata for game referenced in Match object: " + theMatchObject.getString("gameMetaURL"));
            }
        } else {
            this.theGame = theGame;
        }

        if (theMatchObject.has("previewClock")) {
            this.previewClock = theMatchObject.getInt("previewClock");
        } else {
            this.previewClock = -1;
        }

        this.startTime = new Date(theMatchObject.getLong("startTime"));
        this.randomToken = theMatchObject.getString("randomToken");
        this.spectatorAuthToken = authToken;
        this.isCompleted = theMatchObject.getBoolean("isCompleted");
        if (theMatchObject.has("isAborted")) {
            this.isAborted = theMatchObject.getBoolean("isAborted");
        } else {
            this.isAborted = false;
        }
        if (theMatchObject.has("tournamentNameFromHost")) {
            this.tournamentNameFromHost = theMatchObject.getString("tournamentNameFromHost");
        } else {
            this.tournamentNameFromHost = null;
        }

        this.numRoles = Role.computeRoles(this.theGame.getRules()).size();

        this.moveHistory = new ArrayList>();
        this.stateHistory = new ArrayList>();
        this.stateTimeHistory = new ArrayList();
        this.errorHistory = new ArrayList>();

        JSONArray theMoves = theMatchObject.getJSONArray("moves");
        for (int i = 0; i < theMoves.length(); i++) {
            List theMove = new ArrayList();
            JSONArray moveElements = theMoves.getJSONArray(i);
            for (int j = 0; j < moveElements.length(); j++) {
                theMove.add(GdlFactory.createTerm(moveElements.getString(j)));
            }
            moveHistory.add(theMove);
        }
        JSONArray theStates = theMatchObject.getJSONArray("states");
        for (int i = 0; i < theStates.length(); i++) {
            Set theState = new HashSet();
            SymbolList stateElements = (SymbolList) SymbolFactory.create(theStates.getString(i));
            for (int j = 0; j < stateElements.size(); j++)
            {
                theState.add((GdlSentence)GdlFactory.create("( true " + stateElements.get(j).toString() + " )"));
            }
            stateHistory.add(theState);
        }
        JSONArray theStateTimes = theMatchObject.getJSONArray("stateTimes");
        for (int i = 0; i < theStateTimes.length(); i++) {
            this.stateTimeHistory.add(new Date(theStateTimes.getLong(i)));
        }
        if (theMatchObject.has("errors")) {
            JSONArray theErrors = theMatchObject.getJSONArray("errors");
            for (int i = 0; i < theErrors.length(); i++) {
                List theMoveErrors = new ArrayList();
                JSONArray errorElements = theErrors.getJSONArray(i);
                for (int j = 0; j < errorElements.length(); j++)
                {
                    theMoveErrors.add(errorElements.getString(j));
                }
                errorHistory.add(theMoveErrors);
            }
        }

        this.goalValues = new ArrayList();
        try {
            JSONArray theGoalValues = theMatchObject.getJSONArray("goalValues");
            for (int i = 0; i < theGoalValues.length(); i++) {
                this.goalValues.add(theGoalValues.getInt(i));
            }
        } catch (JSONException e) {}

        // TODO: Add a way to recover cryptographic public keys and signatures.
        // Or, perhaps loading a match into memory for editing should strip those?

        if (theMatchObject.has("playerNamesFromHost")) {
            thePlayerNamesFromHost = new ArrayList();
            JSONArray thePlayerNames = theMatchObject.getJSONArray("playerNamesFromHost");
            for (int i = 0; i < thePlayerNames.length(); i++) {
                thePlayerNamesFromHost.add(thePlayerNames.getString(i));
            }
        }
        if (theMatchObject.has("isPlayerHuman")) {
            isPlayerHuman = new ArrayList();
            JSONArray isPlayerHumanArray = theMatchObject.getJSONArray("isPlayerHuman");
            for (int i = 0; i < isPlayerHumanArray.length(); i++) {
                isPlayerHuman.add(isPlayerHumanArray.getBoolean(i));
            }
        }
    }

    /* Mutators */

    public void setCryptographicKeys(EncodedKeyPair k) {
        this.theCryptographicKeys = k;
    }

    public void enableScrambling() {
        theGdlScrambler = new MappingGdlScrambler(new Random(startTime.getTime()));
        for (Gdl rule : theGame.getRules()) {
            theGdlScrambler.scramble(rule);
        }
    }

    public void setPlayerNamesFromHost(List thePlayerNames) {
        this.thePlayerNamesFromHost = thePlayerNames;
    }

    public List getPlayerNamesFromHost() {
        return thePlayerNamesFromHost;
    }

    public void setWhichPlayersAreHuman(List isPlayerHuman) {
        this.isPlayerHuman = isPlayerHuman;
    }

    public void appendMoves(List moves) {
        moveHistory.add(moves);
    }

    public void appendMoves2(List moves) {
        // NOTE: This is appendMoves2 because it Java can't handle two
        // appendMove methods that both take List objects with different
        // templatized parameters.
        List theMoves = new ArrayList();
        for(Move m : moves) {
            theMoves.add(m.getContents());
        }
        appendMoves(theMoves);
    }

    public void appendState(Set state) {
        stateHistory.add(state);
        stateTimeHistory.add(new Date());
    }

    public void appendErrors(List errors) {
        errorHistory.add(errors);
    }

    public void appendNoErrors() {
        List theNoErrors = new ArrayList();
        for (int i = 0; i < this.numRoles; i++) {
            theNoErrors.add("");
        }
        errorHistory.add(theNoErrors);
    }

    public void markCompleted(List theGoalValues) {
        this.isCompleted = true;
        if (theGoalValues != null) {
            this.goalValues.addAll(theGoalValues);
        }
    }

    public void markAborted() {
        this.isAborted = true;
    }

    /* Complex accessors */

    public String toJSON() {
        JSONObject theJSON = new JSONObject();

        try {
            theJSON.put("matchId", matchId);
            theJSON.put("randomToken", randomToken);
            theJSON.put("startTime", startTime.getTime());
            theJSON.put("gameMetaURL", getGameRepositoryURL());
            theJSON.put("isCompleted", isCompleted);
            theJSON.put("isAborted", isAborted);
            theJSON.put("states", new JSONArray(renderArrayAsJSON(renderStateHistory(stateHistory), true)));
            theJSON.put("moves", new JSONArray(renderArrayAsJSON(renderMoveHistory(moveHistory), false)));
            theJSON.put("stateTimes", new JSONArray(renderArrayAsJSON(stateTimeHistory, false)));
            if (!errorHistory.isEmpty()) {
                theJSON.put("errors", new JSONArray(renderArrayAsJSON(renderErrorHistory(errorHistory), false)));
            }
            if (!goalValues.isEmpty()) {
                theJSON.put("goalValues", goalValues);
            }
            theJSON.put("previewClock", previewClock);
            theJSON.put("startClock", startClock);
            theJSON.put("playClock", playClock);
            if (thePlayerNamesFromHost != null) {
                theJSON.put("playerNamesFromHost", thePlayerNamesFromHost);
            }
            if (isPlayerHuman != null) {
                theJSON.put("isPlayerHuman", isPlayerHuman);
            }
            if (tournamentNameFromHost != null) {
                theJSON.put("tournamentNameFromHost", tournamentNameFromHost);
            }
            theJSON.put("scrambled", theGdlScrambler != null ? theGdlScrambler.scrambles() : false);
        } catch (JSONException e) {
            return null;
        }

        if (theCryptographicKeys != null) {
            try {
                SignableJSON.signJSON(theJSON, theCryptographicKeys.thePublicKey, theCryptographicKeys.thePrivateKey);
                if (!SignableJSON.isSignedJSON(theJSON)) {
                    throw new Exception("Could not recognize signed match: " + theJSON);
                }
                if (!SignableJSON.verifySignedJSON(theJSON)) {
                    throw new Exception("Could not verify signed match: " + theJSON);
                }
            } catch (Exception e) {
                System.err.println(e);
                theJSON.remove("matchHostPK");
                theJSON.remove("matchHostSignature");
            }
        }

        return theJSON.toString();
    }

    public String toXML() {
        try {
            JSONObject theJSON = new JSONObject(toJSON());

            StringBuilder theXML = new StringBuilder();
            theXML.append("");
            for (String key : JSONObject.getNames(theJSON)) {
                Object value = theJSON.get(key);
                if (value instanceof JSONObject) {
                    throw new RuntimeException("Unexpected embedded JSONObject in match JSON with tag " + key + "; could not convert to XML.");
                } else if (!(value instanceof JSONArray)) {
                    theXML.append(renderLeafXML(key, theJSON.get(key)));
                } else if (key.equals("states")) {
                    theXML.append(renderStateHistoryXML(stateHistory));
                } else if (key.equals("moves")) {
                    theXML.append(renderMoveHistoryXML(moveHistory));
                } else if (key.equals("errors")) {
                    theXML.append(renderErrorHistoryXML(errorHistory));
                } else {
                    theXML.append(renderArrayXML(key, (JSONArray)value));
                }
            }
            theXML.append("");

            return theXML.toString();
        } catch (JSONException je) {
            return null;
        }
    }

    public List getMostRecentMoves() {
        if (moveHistory.isEmpty())
            return null;
        return moveHistory.get(moveHistory.size()-1);
    }

    public Set getMostRecentState() {
        if (stateHistory.isEmpty())
            return null;
        return stateHistory.get(stateHistory.size()-1);
    }

    public String getGameRepositoryURL() {
        return getGame().getRepositoryURL();
    }

    @Override
    public String toString() {
        return toJSON();
    }

    /* Simple accessors */

    public String getMatchId() {
        return matchId;
    }

    public String getRandomToken() {
        return randomToken;
    }

    public String getSpectatorAuthToken() {
        return spectatorAuthToken;
    }

    public Game getGame() {
        return theGame;
    }

    public List> getMoveHistory() {
        return moveHistory;
    }

    public List> getStateHistory() {
        return stateHistory;
    }

    public List getStateTimeHistory() {
        return stateTimeHistory;
    }

    public List> getErrorHistory() {
        return errorHistory;
    }

    public int getPreviewClock() {
        return previewClock;
    }

    public int getPlayClock() {
        return playClock;
    }

    public int getStartClock() {
        return startClock;
    }

    public Date getStartTime() {
        return startTime;
    }

    public String getTournamentNameFromHost() {
        return tournamentNameFromHost;
    }

    public boolean isCompleted() {
        return isCompleted;
    }

    public boolean isAborted() {
        return isAborted;
    }

    public List getGoalValues() {
        return goalValues;
    }

    public GdlScrambler getGdlScrambler() {
        return theGdlScrambler;
    }

    /* Static methods */

    public static final String getRandomString(int nLength) {
        Random theGenerator = new Random();
        String theString = "";
        for (int i = 0; i < nLength; i++) {
            int nVal = theGenerator.nextInt(62);
            if (nVal < 26) theString += (char)('a' + nVal);
            else if (nVal < 52) theString += (char)('A' + (nVal-26));
            else if (nVal < 62) theString += (char)('0' + (nVal-52));
        }
        return theString;
    }

    /* JSON rendering methods */

    private static final String renderArrayAsJSON(List theList, boolean useQuotes) {
        String s = "[";
        for (int i = 0; i < theList.size(); i++) {
            Object o = theList.get(i);
            // AppEngine-specific, not needed yet: if (o instanceof Text) o = ((Text)o).getValue();
            if (o instanceof Date) o = ((Date)o).getTime();

            if (useQuotes) s += "\"";
            s += o.toString();
            if (useQuotes) s += "\"";

            if (i < theList.size() - 1)
                s += ", ";
        }
        return s + "]";
    }

    private static final List renderStateHistory(List> stateHistory) {
        List renderedStates = new ArrayList();
        for (Set aState : stateHistory) {
            renderedStates.add(renderStateAsSymbolList(aState));
        }
        return renderedStates;
    }

    private static final List renderMoveHistory(List> moveHistory) {
        List renderedMoves = new ArrayList();
        for (List aMove : moveHistory) {
            renderedMoves.add(renderArrayAsJSON(aMove, true));
        }
        return renderedMoves;
    }

    private static final List renderErrorHistory(List> errorHistory) {
        List renderedErrors = new ArrayList();
        for (List anError : errorHistory) {
            renderedErrors.add(renderArrayAsJSON(anError, true));
        }
        return renderedErrors;
    }

    private static final String renderStateAsSymbolList(Set theState) {
        // Strip out the TRUE proposition, since those are implied for states.
        String s = "( ";
        for (GdlSentence sent : theState) {
            String sentString = sent.toString();
            s += sentString.substring(6, sentString.length()-2).trim() + " ";
        }
        return s + ")";
    }

    /* XML Rendering methods -- these are horribly inefficient and are included only for legacy/standards compatibility */

    private static final String renderLeafXML(String tagName, Object value) {
        return "<" + tagName + ">" + value.toString() + "";
    }

    private static final String renderMoveHistoryXML(List> moveHistory) {
        StringBuilder theXML = new StringBuilder();
        theXML.append("");
        for (List move : moveHistory) {
            theXML.append("");
            for (GdlTerm action : move) {
                theXML.append(renderLeafXML("action", renderGdlToXML(action)));
            }
            theXML.append("");
        }
        theXML.append("");
        return theXML.toString();
    }

    private static final String renderErrorHistoryXML(List> errorHistory) {
        StringBuilder theXML = new StringBuilder();
        theXML.append("");
        for (List errors : errorHistory) {
            theXML.append("");
            for (String error : errors) {
                theXML.append(renderLeafXML("error", error));
            }
            theXML.append("");
        }
        theXML.append("");
        return theXML.toString();
    }

    private static final String renderStateHistoryXML(List> stateHistory) {
        StringBuilder theXML = new StringBuilder();
        theXML.append("");
        for (Set state : stateHistory) {
            theXML.append(renderStateXML(state));
        }
        theXML.append("");
        return theXML.toString();
    }

    public static final String renderStateXML(Set state) {
        StringBuilder theXML = new StringBuilder();
        theXML.append("");
        for (GdlSentence sentence : state) {
            theXML.append(renderGdlToXML(sentence));
        }
        theXML.append("");
        return theXML.toString();
    }

    private static final String renderArrayXML(String tag, JSONArray arr) throws JSONException {
        StringBuilder theXML = new StringBuilder();
        for (int i = 0; i < arr.length(); i++) {
            theXML.append(renderLeafXML(tag, arr.get(i)));
        }
        return theXML.toString();
    }

    private static final String renderGdlToXML(Gdl gdl) {
        String rval = "";
        if(gdl instanceof GdlConstant) {
            GdlConstant c = (GdlConstant)gdl;
            return c.getValue();
        } else if(gdl instanceof GdlFunction) {
            GdlFunction f = (GdlFunction)gdl;
            if(f.getName().toString().equals("true"))
            {
                return ""+renderGdlToXML(f.get(0))+"";
            }
            else
            {
                rval += ""+f.getName()+"";
                for(int i=0; i";
                return rval;
            }
        } else if (gdl instanceof GdlRelation) {
            GdlRelation relation = (GdlRelation) gdl;
            if(relation.getName().toString().equals("true"))
            {
                for(int i=0; i";
                return rval;
            } else {
                rval+=""+relation.getName()+"";
                for(int i=0; i";
                return rval;
            }
        } else {
            System.err.println("gdlToXML Error: could not handle "+gdl.toString());
            return null;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy