package util.propnet.architecture; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.io.Serializable; import java.util.List; import java.util.Set; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import util.gdl.grammar.GdlConstant; import util.gdl.grammar.GdlFunction; import util.gdl.grammar.GdlProposition; import util.gdl.grammar.GdlTerm; import util.logging.GamerLogger; import util.propnet.architecture.components.And; import util.propnet.architecture.components.Not; import util.propnet.architecture.components.Or; import util.propnet.architecture.components.Proposition; import util.propnet.architecture.components.Transition; import util.statemachine.Role; /** * The PropNet class is designed to represent Propositional Networks. * * A propositional shef.network (also known as a "propnet") is a way of representing * a game as a logic circuit. States of the game are represented by assignments * of TRUE or FALSE to "base" propositions, each of which represents a single * fact that can be true about the state of the game. For example, in a game of * Tic-Tac-Toe, the fact (cell 1 1 x) indicates that the cell (1,1) has an 'x' * in it. That fact would correspond to a base proposition, which would be set * to TRUE to indicate that the fact is true in the current state of the game. * Likewise, the base corresponding to the fact (cell 1 1 o) would be false, * because in that state of the game there isn't an 'o' in the cell (1,1). * * A state of the game is uniquely determined by the assignment of truth values * to the base propositions in the propositional shef.network. Every assignment of * truth values to base propositions corresponds to exactly one unique state of * the game. * * Given the values of the base propositions, you can use the connections in * the shef.network (AND gates, OR gates, NOT gates) to determine the truth values * of other propositions. For example, you can determine whether the terminal * proposition is true: if that proposition is true, the game is over when it * reaches this state. Otherwise, if it is false, the game isn't over. You can * also determine the value of the goal propositions, which represent facts * like (goal xplayer 100). If that proposition is true, then that fact is true * in this state of the game, which means that xplayer has 100 points. * * You can also use a propositional shef.network to determine the next state of the * game, given the current state and the roles for each player. First, you set * the input propositions which correspond to each move to TRUE. Once that has * been done, you can determine the truth value of the transitions. Each base * proposition has a "transition" component going into it. This transition has * the truth value that its base will take on in the next state of the game. * * For further information about propositional networks, see: * * "Decomposition of Games for Efficient Reasoning" by Eric Schkufza. * "Factoring General Games using Propositional Automata" by Evan Cox et al. * * @author Sam Schreiber */ public final class PropNet implements Serializable { private static final long serialVersionUID = -6425163053203784512L; /** References to every component in the PropNet. */ private final Set<Component> components; /** References to every Proposition in the PropNet. */ private final Set<Proposition> propositions; /** References to every BaseProposition in the PropNet, indexed by name. */ private final Map<GdlTerm, Proposition> basePropositions; /** References to every InputProposition in the PropNet, indexed by name. */ private final Map<GdlTerm, Proposition> inputPropositions; /** References to every LegalProposition in the PropNet, indexed by role. */ private final Map<Role, Set<Proposition>> legalPropositions; /** References to every GoalProposition in the PropNet, indexed by role. */ private final Map<Role, Set<Proposition>> goalPropositions; /** A reference to the single, unique, InitProposition. */ private final Proposition initProposition; /** A reference to the single, unique, TerminalProposition. */ private final Proposition terminalProposition; /** A helper mapping between input/legal propositions. */ private final Map<Proposition, Proposition> legalInputMap; /** A helper list of all of the roles. */ private final List<Role> roles; public void addComponent(Component c) { components.add(c); if (c instanceof Proposition) propositions.add((Proposition)c); } /** * Creates a new PropNet from a list of Components, along with indices over * those components. * * @param components * A list of Components. */ public PropNet(List<Role> roles, Set<Component> components) { this.roles = roles; this.components = components; this.propositions = recordPropositions(); this.basePropositions = recordBasePropositions(); this.inputPropositions = recordInputPropositions(); this.legalPropositions = recordLegalPropositions(); this.goalPropositions = recordGoalPropositions(); this.initProposition = recordInitProposition(); this.terminalProposition = recordTerminalProposition(); this.legalInputMap = makeLegalInputMap(); } public List<Role> getRoles() { return roles; } public Map<Proposition, Proposition> getLegalInputMap() { return legalInputMap; } private Map<Proposition, Proposition> makeLegalInputMap() { Map<Proposition, Proposition> legalInputMap = new HashMap<Proposition, Proposition>(); /*for (Proposition inputProp : inputPropositions.values()) { List<GdlTerm> inputPropBody = ((GdlFunction)inputProp.getName()).getBody(); for (Set<Proposition> legalProps : legalPropositions.values()) { for (Proposition legalProp : legalProps) { List<GdlTerm> legalPropBody = ((GdlFunction)legalProp.getName()).getBody(); if (legalPropBody.equals(inputPropBody)) { legalInputMap.put(inputProp, legalProp); legalInputMap.put(legalProp, inputProp); } } } }*/ //This function hangs on certain games (e.g. knightfight) //Let's speed it up a bit, shall we... Map<List<GdlTerm>, Proposition> inputPropsByBody = new HashMap<List<GdlTerm>, Proposition>(); for(Proposition inputProp : inputPropositions.values()) { List<GdlTerm> inputPropBody = ((GdlFunction)inputProp.getName()).getBody(); inputPropsByBody.put(inputPropBody, inputProp); } for(Set<Proposition> legalProps : legalPropositions.values()) { for(Proposition legalProp : legalProps) { List<GdlTerm> legalPropBody = ((GdlFunction)legalProp.getName()).getBody(); Proposition inputProp = inputPropsByBody.get(legalPropBody); legalInputMap.put(inputProp, legalProp); legalInputMap.put(legalProp, inputProp); } } return legalInputMap; } /** * Getter method. * * @return References to every BaseProposition in the PropNet, indexed by * name. */ public Map<GdlTerm, Proposition> getBasePropositions() { return basePropositions; } /** * Getter method. * * @return References to every Component in the PropNet. */ public Set<Component> getComponents() { return components; } /** * Getter method. * * @return References to every GoalProposition in the PropNet, indexed by * player name. */ public Map<Role, Set<Proposition>> getGoalPropositions() { return goalPropositions; } /** * Getter method. A reference to the single, unique, InitProposition. * * @return */ public Proposition getInitProposition() { return initProposition; } /** * Getter method. * * @return References to every InputProposition in the PropNet, indexed by * name. */ public Map<GdlTerm, Proposition> getInputPropositions() { return inputPropositions; } /** * Getter method. * * @return References to every LegalProposition in the PropNet, indexed by * player name. */ public Map<Role, Set<Proposition>> getLegalPropositions() { return legalPropositions; } /** * Getter method. * * @return References to every Proposition in the PropNet. */ public Set<Proposition> getPropositions() { return propositions; } /** * Getter method. * * @return A reference to the single, unique, TerminalProposition. */ public Proposition getTerminalProposition() { return terminalProposition; } /** * Returns a representation of the PropNet in .dot format. * * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("digraph propNet\n{\n"); for ( Component component : components ) { sb.append("\t" + component.toString() + "\n"); } sb.append("}"); return sb.toString(); } /** * Outputs the propnet in .dot format to a particular file. * This can be viewed with tools like Graphviz and ZGRViewer. * * @param filename the name of the file to output to */ public void renderToFile(String filename) { try { File f = new File(filename); FileOutputStream fos = new FileOutputStream(f); OutputStreamWriter fout = new OutputStreamWriter(fos, "UTF-8"); fout.write(toString()); fout.close(); fos.close(); } catch(Exception e) { GamerLogger.logStackTrace("StateMachine", e); } } /** * Builds an index over the BasePropositions in the PropNet. * * @return An index over the BasePropositions in the PropNet. */ private Map<GdlTerm, Proposition> recordBasePropositions() { Map<GdlTerm, Proposition> basePropositions = new HashMap<GdlTerm, Proposition>(); for ( Proposition proposition : propositions ) { if ( proposition.getInputs().size() > 0 ) { Component component = proposition.getSingleInput(); if ( component instanceof Transition ) { basePropositions.put(proposition.getName(), proposition); } /*if(proposition.getName() instanceof GdlFunction) { if(((GdlFunction)proposition.getName()).equals(GdlPool.getConstant("true"))) basePropositions.put(proposition.getName(), proposition); }*/ } } return basePropositions; } /** * Builds an index over the GoalPropositions in the PropNet. * * @return An index over the GoalPropositions in the PropNet. */ private Map<Role, Set<Proposition>> recordGoalPropositions() { Map<Role, Set<Proposition>> goalPropositions = new HashMap<Role, Set<Proposition>>(); for ( Proposition proposition : propositions ) { if ( proposition.getName() instanceof GdlFunction ) { GdlFunction function = (GdlFunction) proposition.getName(); if ( function.getName().getValue().equalsIgnoreCase("goal") ) { GdlConstant name = (GdlConstant) function.get(0); GdlProposition prop = (GdlProposition)name.toSentence(); Role r = new Role(prop); if ( !goalPropositions.containsKey(r) ) { goalPropositions.put(r, new HashSet<Proposition>()); } goalPropositions.get(r).add(proposition); } } } return goalPropositions; } /** * Returns a reference to the single, unique, InitProposition. * * @return A reference to the single, unique, InitProposition. */ private Proposition recordInitProposition() { for ( Proposition proposition : propositions ) { if ( proposition.getName() instanceof GdlConstant ) { GdlConstant constant = (GdlConstant) proposition.getName(); if ( constant.getValue().equalsIgnoreCase("INIT") ) { return proposition; } } } return null; } /** * Builds an index over the InputPropositions in the PropNet. * * @return An index over the InputPropositions in the PropNet. */ private Map<GdlTerm, Proposition> recordInputPropositions() { Map<GdlTerm, Proposition> inputPropositions = new HashMap<GdlTerm, Proposition>(); for ( Proposition proposition : propositions ) { if ( proposition.getName() instanceof GdlFunction ) { GdlFunction function = (GdlFunction) proposition.getName(); if ( function.getName().getValue().equalsIgnoreCase("does") ) { inputPropositions.put(proposition.getName(), proposition); } } } return inputPropositions; } /** * Builds an index over the LegalPropositions in the PropNet. * * @return An index over the LegalPropositions in the PropNet. */ private Map<Role, Set<Proposition>> recordLegalPropositions() { Map<Role, Set<Proposition>> legalPropositions = new HashMap<Role, Set<Proposition>>(); for ( Proposition proposition : propositions ) { if ( proposition.getName() instanceof GdlFunction ) { GdlFunction function = (GdlFunction) proposition.getName(); if ( function.getName().getValue().equalsIgnoreCase("legal") ) { GdlConstant name = (GdlConstant) function.get(0); GdlProposition prop = (GdlProposition)name.toSentence(); Role r = new Role(prop); if ( !legalPropositions.containsKey(r) ) { legalPropositions.put(r, new HashSet<Proposition>()); } legalPropositions.get(r).add(proposition); } } } return legalPropositions; } /** * Builds an index over the Propositions in the PropNet. * * @return An index over Propositions in the PropNet. */ private Set<Proposition> recordPropositions() { Set<Proposition> propositions = new HashSet<Proposition>(); for ( Component component : components ) { if ( component instanceof Proposition ) { propositions.add((Proposition) component); } } return propositions; } /** * Records a reference to the single, unique, TerminalProposition. * * @return A reference to the single, unqiue, TerminalProposition. */ private Proposition recordTerminalProposition() { for ( Proposition proposition : propositions ) { if ( proposition.getName() instanceof GdlConstant ) { GdlConstant constant = (GdlConstant) proposition.getName(); if ( constant.getValue().equalsIgnoreCase("terminal") ) { return proposition; } } } return null; } public int getSize() { return components.size(); } public int getNumAnds() { int andCount = 0; for(Component c : components) { if(c instanceof And) andCount++; } return andCount; } public int getNumOrs() { int orCount = 0; for(Component c : components) { if(c instanceof Or) orCount++; } return orCount; } public int getNumNots() { int notCount = 0; for(Component c : components) { if(c instanceof Not) notCount++; } return notCount; } public int getNumLinks() { int linkCount = 0; for(Component c : components) { linkCount += c.getOutputs().size(); } return linkCount; } }