package util.statemachine; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import util.gdl.grammar.Gdl; import util.gdl.grammar.GdlProposition; import util.gdl.grammar.GdlSentence; import util.statemachine.exceptions.GoalDefinitionException; import util.statemachine.exceptions.MoveDefinitionException; import util.statemachine.exceptions.TransitionDefinitionException; /** * Provides the base class for all state machine implementations. */ public abstract class StateMachine { // ============================================ // Stubs for implementations // ============================================ // The following methods are required for a valid // state machine implementation. public abstract void initialize(List<Gdl> description); public abstract int getGoal(MachineState state, Role role) throws GoalDefinitionException; public abstract boolean isTerminal(MachineState state); public abstract List<Role> getRoles(); public abstract MachineState getInitialState(); // TODO: There are philosophical reasons for this to return Set<Move> rather than List<Move>. public abstract List<Move> getLegalMoves(MachineState state, Role role) throws MoveDefinitionException; public abstract MachineState getNextState(MachineState state, List<Move> moves) throws TransitionDefinitionException; // The following methods are included in the abstract StateMachine base so // implementations which use alternative Role/Move/StateModel representations // can look up/compute what some Gdl corresponds to in their representation. public abstract Role getRoleFromProp(GdlProposition proposition); public abstract Move getMoveFromSentence(GdlSentence sentence); public abstract MachineState getMachineStateFromSentenceList(Set<GdlSentence> sentenceList); // ============================================ // Stubs for advanced methods // ============================================ // // The following methods have functioning stubs, // which can be overridden with full-fledged versions // as needed by state machines. Clients should assume // the contracts for these methods hold, regardless // of the state machine implementation they pick. // Override this to perform some extra work (like trimming a cache) once per move. // CONTRACT: Should be called once per move. public void doPerMoveWork() {} // Override this to provide memory-saving destructive-next-state functionality. // CONTRACT: After calling this method, "state" should not be accessed. public MachineState getNextStateDestructively(MachineState state, List<Move> moves) throws TransitionDefinitionException { return getNextState(state, moves); } // Override this to allow the state machine to be conditioned on a particular current state. // This means that the state machine will only handle portions of the game tree at and below // the given state; it no longer needs to properly handle earlier portions of the game tree. // This constraint can be used to optimize certain state machine implementations. // CONTRACT: After calling this method, the state machine never deals with a state that // is not "theState" or one of its descendants in the game tree. public void updateRoot(MachineState theState) { ; } // ============================================ // Implementations of convenience methods // ============================================ public String getName() { return this.getClass().getSimpleName(); } public List<List<Move>> getLegalJointMoves(MachineState state) throws MoveDefinitionException { final List<List<Move>> legals = new ArrayList<List<Move>>(); for (Role role : getRoles()) { legals.add(getLegalMoves(state, role)); } final List<List<Move>> crossProduct = new ArrayList<List<Move>>(); crossProductLegalMoves(legals, crossProduct, new LinkedList<Move>()); return crossProduct; } public List<List<Move>> getLegalJointMoves(MachineState state, Role role, Move move) throws MoveDefinitionException { List<List<Move>> legals = new ArrayList<List<Move>>(); for (Role r : getRoles()) { if (r.equals(role)) { List<Move> m = new ArrayList<Move>(); m.add(move); legals.add(m); } else { legals.add(getLegalMoves(state, r)); } } List<List<Move>> crossProduct = new ArrayList<List<Move>>(); crossProductLegalMoves(legals, crossProduct, new LinkedList<Move>()); return crossProduct; } public List<MachineState> getNextStates(MachineState state) throws MoveDefinitionException, TransitionDefinitionException { List<MachineState> nextStates = new ArrayList<MachineState>(); for (List<Move> move : getLegalJointMoves(state)) { nextStates.add(getNextState(state, move)); } return nextStates; } public Map<Move, List<MachineState>> getNextStates(MachineState state, Role role) throws MoveDefinitionException, TransitionDefinitionException { Map<Move, List<MachineState>> nextStates = new HashMap<Move, List<MachineState>>(); Map<Role, Integer> roleIndices = getRoleIndices(); for (List<Move> moves : getLegalJointMoves(state)) { Move move = moves.get(roleIndices.get(role)); if (!nextStates.containsKey(move)) { nextStates.put(move, new ArrayList<MachineState>()); } nextStates.get(move).add(getNextState(state, moves)); } return nextStates; } protected void crossProductLegalMoves(List<List<Move>> legals, List<List<Move>> crossProduct, LinkedList<Move> partial) { if (partial.size() == legals.size()) { crossProduct.add(new ArrayList<Move>(partial)); } else { for (Move move : legals.get(partial.size())) { partial.addLast(move); crossProductLegalMoves(legals, crossProduct, partial); partial.removeLast(); } } } private Map<Role,Integer> roleIndices = null; public Map<Role, Integer> getRoleIndices() { if(roleIndices == null) { roleIndices = new HashMap<Role, Integer>(); List<Role> roles = getRoles(); for (int i = 0; i < roles.size(); i++) { roleIndices.put(roles.get(i), i); } } return roleIndices; } public List<Integer> getGoals(MachineState state) throws GoalDefinitionException { List<Integer> theGoals = new ArrayList<Integer>(); for (Role r : getRoles()) { theGoals.add(getGoal(state, r)); } return theGoals; } public List<Double> getDoubleGoals(MachineState state) throws GoalDefinitionException { List<Double> theGoals = new ArrayList<Double>(); for (Role r : getRoles()) { theGoals.add((double)getGoal(state, r)); } return theGoals; } public List<Move> getRandomJointMove(MachineState state) throws MoveDefinitionException { List<Move> random = new ArrayList<Move>(); for (Role role : getRoles()) { random.add(getRandomMove(state, role)); } return random; } public List<Move> getRandomJointMove(MachineState state, Role role, Move move) throws MoveDefinitionException { List<Move> random = new ArrayList<Move>(); for (Role r : getRoles()) { if (r.equals(role)) { random.add(move); } else { random.add(getRandomMove(state, r)); } } return random; } public Move getRandomMove(MachineState state, Role role) throws MoveDefinitionException { List<Move> legals = getLegalMoves(state, role); return legals.get(new Random().nextInt(legals.size())); } public MachineState getRandomNextState(MachineState state) throws MoveDefinitionException, TransitionDefinitionException { List<Move> random = getRandomJointMove(state); return getNextState(state, random); } public MachineState getRandomNextState(MachineState state, Role role, Move move) throws MoveDefinitionException, TransitionDefinitionException { List<Move> random = getRandomJointMove(state, role, move); return getNextState(state, random); } public MachineState performDepthCharge(MachineState state, int[] theDepth) throws TransitionDefinitionException, MoveDefinitionException { int nDepth = 0; while(!isTerminal(state)) { nDepth++; state = getNextStateDestructively(state, getRandomJointMove(state)); } if(theDepth != null) theDepth[0] = nDepth; return state; } }