![JAR search and dependency download from the Maven repository](/logo.png)
org.ggp.base.util.match.Match Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of alloy-ggp-base Show documentation
Show all versions of alloy-ggp-base Show documentation
A modified version of the GGP-Base library for Alloy.
The 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() + "" + tagName + ">";
}
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"+renderGdlToXML(f.get(i))+"";
return rval;
}
} else if (gdl instanceof GdlRelation) {
GdlRelation relation = (GdlRelation) gdl;
if(relation.getName().toString().equals("true"))
{
for(int i=0; i"+renderGdlToXML(relation.get(i))+"";
return rval;
} else {
rval+=""+relation.getName()+" ";
for(int i=0; i"+renderGdlToXML(relation.get(i))+"";
return rval;
}
} else {
System.err.println("gdlToXML Error: could not handle "+gdl.toString());
return null;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy