Package cz.matfyz.aai.fantom.game

Source Code of cz.matfyz.aai.fantom.game.Graph$Edge

/*
   This file is part of Fantom.

    Fantom is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Fantom is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Fantom.  If not, see <http://www.gnu.org/licenses/>.
*/
package cz.matfyz.aai.fantom.game;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;

import cz.matfyz.aai.fantom.message.ClientType;
import cz.matfyz.aai.fantom.message.MessageMoveBase;
import cz.matfyz.aai.fantom.message.MessageUpdate;
import cz.matfyz.aai.fantom.message.MessageMoveBase.ActorMove;
import cz.matfyz.aai.fantom.message.MessageUpdate.ActorTickets;
import cz.matfyz.aai.fantom.server.Client;
import cz.matfyz.aai.fantom.server.ProtocolException;
import cz.matfyz.aai.fantom.utils.Parser;
import cz.matfyz.aai.fantom.utils.ParserException;

/**
* The game graph. Contains specification of the nodes, edges and transport
* types.
*/
public class Graph {
  /**
   * The name of the property that is used, when the line contains a node.
   */
  public static final String PROPERTY_NODE = "node";
 
  /**
   * The name of the property that is used, when the line contains a node.
   */
  public static final String PROPERTY_NODES = "nodes";
 
  /**
   * The name of the property that is used, when the line contains an edge.
   */
  public static final String PROPERTY_EDGE = "edge";
 
  /**
   * The name of the property that is used, when the line contains
   * a transport type.
   */
  public static final String PROPERTY_TRANSPORT = "transport";
 
  /**
   * The name of the property that is used, when the line contains
   * specification of the game.
   */
  public static final String PROPERTY_GAME = "game";
 
  /**
   * The name of the property that is used, when the line contains
   * information about the phantom.
   */
  public static final String PROPERTY_PHANTOM = "phantom";
 
  /**
   * The name of the property that is used to store the current position
   * of an actor.
   */
  public static final String PROPERTY_POSITION = "position";
 
  /**
   * The name of the property that is used, when the line contains
   * information about a detective.
   */
  public static final String PROPERTY_DETECTIVE = "detective";

  /**
   * The name of a property that is used to specify an amount of double tickets of an actor.
   */
  public static final String PROPERTY_DOUBLE_TICKET = "double";

  /**
   * The name of a property that is used to specify an amount of universal tickets of an actor.
   */
  public static final String PROPERTY_UNIVERSAL_TICKET = "universal";

  /**
   * The name of the property that contains numbers of the turns, in
   * which the phantom is revealed.
   */
  public static final String PROPERTY_REVEALS = "reveal";

  /**
   * Multiple nodes in the graph.
   */
  public class Nodes {
    /** The name of the property that contains the lowest node id in the range. */
    public static final String PROPERTY_FROM = "from";
    /** The name of the property that contains the highest node id in the range. */
    public static final String PROPERTY_TO = "to";
  }
 
  /**
   * A single node in the graph.
   */
  public class Node {
    /**
     * The name of the property that contains the X position of the node.
     */
    public static final String PROPERTY_POSITION_X = "x";
    /**
     * The name of the property that contains the Y position of the node.
     */
    public static final String PROPERTY_POSITION_Y = "y";
   
    /**
     * The ID of this node.
     */
    private String id;
   
    /**
     * Other (custom) properties of the node.
     */
    private Properties properties;
   
    /**
     * The list of edges going to this node.
     */
    private List<Edge> incomingEdges;
    private List<Edge> incomingEdgesReadOnly;
   
     /**
      * The list of edges going from this node.
      */
    private List<Edge> outgoingEdges;
    private List<Edge> outgoingEdgesReadOnly;
   
    @Override
    public int hashCode() {
      return this.id.hashCode();
    }
   
    /**
     * Returns the ID of the node.
     * @return the ID of the node.
     */
    public String getId() {
      return this.id;
    }
   
    /**
     * Returns the list of edges coming to the node.
     * @return the list of edges coming to the node.
     */
    public List<Edge> getIncomingEdges() {
      return this.incomingEdgesReadOnly;
    }
   
    /**
     * Returns the list of edges going from the node.
     * @return the list of edges going from the node.
     */
    public List<Edge> getOutgoingEdges() {
      return this.outgoingEdgesReadOnly;
    }
   
    /**
     * Returns the other properties of the node.
     * @return the other properties of the node.
     */
    public Properties getProperties() {
      return this.properties;
    }
   
    /**
     * Checks if this node is occupied by an actor.
     * @return <code>true</code> if this node is occupied by
     *     an actor; otherwise, <code>false</code>.
     */
    public boolean isOccupied() {
      for(Actor a : Graph.this.actors) {
        if(a.getCurrentPosition() == this)
          return true;
      }
      return false;
    }
   
    /**
     * Checks if this node is occupied by an actor of the given type.
     * @param type the type of actor, for which the check is performed.
     * @return <code>true</code> if this node is occupied by an actor
     *       of type <code>type</code>; otherwise, <code>false</code>.
     */
    public boolean isOccupied(ClientType type) {
      for(Actor a : Graph.this.actors) {
        if(a.getCurrentPosition() == this
            && a.getClientType() == type) {
          return true;
        }
      }
      return false;
    }
   
    /**
     * Initializes the node according to the specification in <code>properties</code>.
     * @param properties the specification of the node.
     * @throws GraphFormatException if there is a problem when parsing the specification.
     */
    protected void loadFromProperties(Properties properties) throws GraphFormatException {
      this.id = properties.getProperty(PROPERTY_NODE);
      if(this.id == null || this.id.isEmpty())
        throw new GraphFormatException("The ID of the graph was not specified");
     
      this.properties = properties;
    }
   
    /**
     * Serializes the node to the format, from which it was loaded.
     * @return the properties collection with the specification of the node.
     */
    public Properties serialize() {
      Properties res = new Properties();
      res.setProperty(PROPERTY_NODE, getId());
      return res;
    }
   
    /**
     * Converts the node to string representation in the same format, that can be
     * decoded using {@link Parser#parseLine(String)} and {@link Node#Node(Properties)}.
     * @return the string representation of the node.
     */
    @Override
    public String toString() {
      Properties props = (Properties)this.properties.clone();
      props.setProperty(PROPERTY_NODE, this.id);
      return Parser.encodeProperties(props);
    }
   
    /**
     * Initializes a new node.
     */
    private Node() {
      this.incomingEdges = new ArrayList<Edge>();
      this.incomingEdgesReadOnly = Collections.unmodifiableList(this.incomingEdges);
      this.outgoingEdges = new ArrayList<Edge>();
      this.outgoingEdgesReadOnly = Collections.unmodifiableList(this.outgoingEdges);     
    }
   
    /**
     * Initializes a new node according to the specification.
     * @param properties the specification of the node.
     * @throws GraphFormatException if there is a problem when parsing the specification.
     */
    protected Node(Properties properties) throws GraphFormatException {
      this();
      loadFromProperties(properties);
    }

    /**
     * Initializes a new node using the given id.
     * @param id
     */
    private Node(String id) {
      this();
      this.id = id;
    }
  }
 
  /**
   * An edge between two nodes.
   */
  public class Edge {
    /**
     * The source node of the edge.
     */
    private Node source;
   
    /**
     * The target node of the edge.
     */
    private Node target;
   
    /**
     * The transport type of this edge.
     */
    private TransportType transportType;
   
    /**
     * Returns the target node of this edge.
     * @return the target node of this edge.
     */
    public Node getTarget() {
      return target;
    }
   
    /**
     * Returns the source node of this edge.
     * @return the source node of this edge.
     */
    public Node getSource() {
      return source;
    }
   
    /**
     * Returns the transport type of this edge.
     * @return the transport type of this edge.
     */
    public TransportType getTransportType() {
      return transportType;
    }
   
    /**
     * Loads the information about the edge from <code>properties</code>.
     * @param properties the specification of the edge.
     * @throws GraphFormatException if there is a problem when processing the properties.
     */
    protected void loadProperties(Properties properties) throws GraphFormatException {
      String description = properties.getProperty(PROPERTY_EDGE);
      if(description == null || description.isEmpty())
        throw new GraphFormatException("The description of the node was not specified.");
      String[] descr = description.split("@");
      if(descr == null || descr.length != 2)
        throw new GraphFormatException("Invalid description of an edge: " + description);
      String[] route = descr[0].split("-");
      if(route == null || route.length != 2 || route[0] == null || route[0].isEmpty() || route[1] == null || route[1].isEmpty())
        throw new GraphFormatException("Invalid description of a route: " + descr[0]);

      String src_id = route[0].trim();
      String dst_id = route[1].trim();

      if(src_id == null || src_id.isEmpty())
        throw new GraphFormatException("The ID of the source node was not specified.");
      this.source = Graph.this.nodes.get(src_id);
      if(this.source == null)
        throw new GraphFormatException(String.format("The node '%s' was not found.", src_id));
     
      if(dst_id == null || dst_id.isEmpty())
        throw new GraphFormatException("The ID of the destination node was not specified.");
      this.target = Graph.this.nodes.get(dst_id);       
      if(this.target == null)
        throw new GraphFormatException(String.format("The node '%s' was not found", dst_id));
     
      String type_str = descr[1];
      if(type_str == null || type_str.isEmpty())
        throw new GraphFormatException("The transport type was not specified.");
      if(type_str.contains("+"))
        throw new GraphFormatException(String.format("Multi-transport description of an edge passed to a single-transport edge constructor: %s", description));
     
      this.transportType = Graph.this.transportTypes.get(type_str);
      if(this.transportType == null)
        throw new GraphFormatException(String.format("The transport type '%s' was not found.", type_str));
    }
   
    /**
     * Serializes the edge to the same format, in from which it was loaded.
     * @return the specification of the edge.
     */
    public Properties serialize() {
      Properties res = new Properties();
      String edgeSpec = String.format("%s-%s@%s", source.getId(), target.getId(), transportType.getName());
      res.setProperty(PROPERTY_EDGE, edgeSpec);
     
      return res;
    }
   
    @Override
    public String toString() {
      Properties props = new Properties();
      props.setProperty(PROPERTY_EDGE, String.format("%s-%s@%s", this.source.getId(), this.target.getId(), this.transportType.getName()));
      return Parser.encodeProperties(props);
    }
   
    /**
     * Creates a new edge.
     * @param source the source node of the edge.
     * @param target the target node of the edge.
     * @param transportType the transport type of the edge.
     */
    protected Edge(Node source, Node target, TransportType transportType) {
      this.source = source;
      this.target = target;
      this.transportType = transportType;
    }
   
    /**
     * Creates a new edge according to the specification in <code>properties</code>.
     * @param properties the specification of the edge.
     * @throws GraphFormatException if there is a problem when parsing the specification.
     */
    protected Edge(Properties properties) throws GraphFormatException {
      loadProperties(properties);
    }
  }
  /**
   * An edge between two nodes, which allows multiple trasport types in the same time.
   * This is an internal helper structure.
   */
  private class MultiEdge {
    /**
     * The source node of the edge.
     */
    private Node source;
   
    /**
     * The target node of the edge.
     */
    private Node target;
   
    /**
     * The transport type of this edge.
     */
    private Collection<TransportType> transportTypes;
   
    private boolean isOriented;
   
    /**
     * Returns the target node of this edge.
     * @return the target node of this edge.
     */
    public Node getTarget() {
      return target;
    }
   
    /**
     * Returns the source node of this edge.
     * @return the source node of this edge.
     */
    public Node getSource() {
      return source;
    }
   
    /**
     * Returns the list of edges represented by this multi-edge.
     * @return the list of edges.
     */
    public Edge[] getEdges() {
      int count = (isOriented ? 1 : 2) * transportTypes.size();
      Edge[] res = new Edge[count];
      int pos = 0;
     
      for(TransportType ttype : transportTypes) {
        res[pos++] = new Edge(getSource(), getTarget(), ttype);
        if(!isOriented)
          res[pos++] = new Edge(getTarget(), getSource(), ttype);
      }
      return res;
    }
   
    /**
     * Returns the transport type of this edge.
     * @return the transport type of this edge.
     */
    public Collection<TransportType> getTransportTypes() {
      return transportTypes;
    }
   
    /**
     * Loads the information about the edge from <code>properties</code>.
     * @param properties the specification of the edge.
     * @throws GraphFormatException if there is a problem when processing the properties.
     */
    protected void loadProperties(Properties properties) throws GraphFormatException {
      String description = properties.getProperty(PROPERTY_EDGE);
      if(description == null || description.isEmpty())
        throw new GraphFormatException("The description of the node was not specified.");
      String[] descr = description.split("@");
      if(descr == null || descr.length != 2)
        throw new GraphFormatException("Invalid description of an edge: " + description);
      String[] route;
      isOriented = descr[0].indexOf('-') >= 0;
      if(isOriented)
        route = descr[0].split("-");
      else
        route = descr[0].split("=");
      if(route == null || route.length != 2 || route[0] == null || route[0].isEmpty() || route[1] == null || route[1].isEmpty())
        throw new GraphFormatException("Invalid description of a route: " + descr[0]);

      String src_id = route[0].trim();
      String dst_id = route[1].trim();

      if(src_id == null || src_id.isEmpty())
        throw new GraphFormatException("The ID of the source node was not specified.");
      this.source = Graph.this.nodes.get(src_id);
      if(this.source == null)
        throw new GraphFormatException(String.format("The node '%s' was not found.", src_id));
     
      if(dst_id == null || dst_id.isEmpty())
        throw new GraphFormatException("The ID of the destination node was not specified.");
      this.target = Graph.this.nodes.get(dst_id);       
      if(this.target == null)
        throw new GraphFormatException(String.format("The node '%s' was not found", dst_id));
     
      String[] types = descr[1].split("\\+");
      if(types == null || types.length == 0)
        throw new GraphFormatException("Missing a transport type: " + description);

      this.transportTypes = new ArrayList<TransportType>(types.length);
      for(String type_str : types) {
        if(type_str == null || type_str.isEmpty())
          throw new GraphFormatException("The transport type was not specified.");
       
        TransportType transportType = Graph.this.transportTypes.get(type_str);
        if(transportType == null)
          throw new GraphFormatException(String.format("The transport type '%s' was not found.", type_str));
        this.transportTypes.add(Graph.this.transportTypes.get(type_str));
      }
    }
   
    @Override
    public String toString() {
      StringBuilder transports = new StringBuilder();
      for(TransportType tt : this.getTransportTypes()) {
        if(transports.length() != 0) {
          transports.append('+');
        }
        transports.append(tt.getName());
      }

      Properties props = new Properties();
      props.setProperty(PROPERTY_EDGE, String.format("%s%s%s@%s", this.source.getId(), this.isOriented ? "-" : "=", this.target.getId(), transports));
      return Parser.encodeProperties(props);
    }
   
    /**
     * Creates a new edge according to the specification in <code>properties</code>.
     * @param properties the specification of the edge.
     * @throws GraphFormatException if there is a problem when parsing the specification.
     */
    protected MultiEdge(Properties properties) throws GraphFormatException {
      loadProperties(properties);
    }
  }
 
  /**
   * The list of actors in the game.
   */
  private List<Actor> actors;
  private List<Actor> actorsReadOnly;
 
  /**
   * The list of edges in the graph.
   */
  private List<Edge> edges;
  private List<Edge> edgesReadOnly;
 
  /**
   * The list of nodes in the graph.
   */
  private Map<String, Node> nodes;
  private Map<String, Node> nodesReadOnly;
 
  /**
   * The list of transport types used in this graph.
   */
  private Map<String, TransportType> transportTypes;
  private Map<String, TransportType> transportTypesReadOnly;
 
  /**
   * The number of turns after which the game ends.
   */
  private int gameLength;
 
  /**
   * The numbers of turns since the beginning of the game, after
   * which the phanom is revealed.
   */
  private Set<Integer> phantomReveals;
  private Set<Integer> phantomRevealsReadonly;
 
  /**
   * Adds universal edges between all nodes, where they did not exist.
   */
  protected void addUniversalEdges() {
    TransportType universal = getTransportType(PROPERTY_UNIVERSAL_TICKET);
    for(Node source : nodes.values()) {
      HashSet<Node> reachableNodes = new HashSet<Node>();
      HashSet<Node> reachableByUniversals = new HashSet<Node>();
      for(Edge e : source.outgoingEdges) {
        if(e.getTransportType() == universal)
          reachableByUniversals.add(e.getTarget());
        else
          reachableNodes.add(e.getTarget());
      }
      for(Node target : reachableNodes) {
        if(reachableByUniversals.contains(target))
          continue;
        Edge e = new Edge(source, target, universal);
        edges.add(e);
        source.outgoingEdges.add(e);
        target.incomingEdges.add(e);
      }
    }
  }
 
  /**
   * Finds out which of the given phantoms are captured in the current
   * state of the game.
   * @return A collection of currently captured phantoms.
   */
  public Collection<Actor> capturedPhantoms() {
    Collection<Actor> captured = new ArrayList<Actor>(getActors().size());

    for(Actor a : getActors()) {
      if(a.isDetective()) {
        continue;
      }

      assert(a.getCurrentPosition() != null);

      for(Actor b : getActors()) {
        if(b.isPhantom()) {
          continue;
        }

        assert(b.getCurrentPosition() != null);

        if(b.getCurrentPosition().equals(a.getCurrentPosition())) {
          captured.add(a);
          break;
        }
      }
    }

    return captured;
  }
 
  /**
   * Creates a clone of the graph. Does not clone the histories
   * of movements of the actors.
   */
  public Object clone() {
    try {
      return new Graph(this.serialize());
    }
    catch(Exception e) {
      throw new IllegalStateException("Failed to clone the graph", e);
    }
  }
 
  /**
   * Returns the actor object with the given ID.
   *
   * @param actorId the ID of the actor.
   * @return the object representing the actor; returns <code>null</code>
   *     if no such actor was found.
   */
  public Actor getActor(String actorId) {
    if(actorId == null)
      throw new IllegalArgumentException("actorId must not be null");
    for(Actor actor : actors) {
      if(actor.getId().equals(actorId))
        return actor;
    }
    return null;
  }
 
  /**
   * Returns the list of actors in the game.
   * @return the list of actors in the game.
   */
  public List<Actor> getActors() {
    return this.actorsReadOnly;
  }
 
  /**
   * Returns the list of actors of the given type.
   * @param type the type of client, for which the actors are returned.
   * @return the list of actors of the given type.
   */
  public List<Actor> getActors(ClientType type) {
    ArrayList<Actor> res = new ArrayList<Actor>();
    for(Actor actor : actors) {
      if(actor.isDetective() && type == ClientType.DETECTIVE)
        res.add(actor);
      else if(actor.isPhantom() && type == ClientType.PHANTOM)
        res.add(actor);
    }
    return res;
  }
 
  /**
   * Returns the list of edges in the game graph.
   * @return the list of edges in the game graph.
   */
  public List<Edge> getEdges() {
    return this.edgesReadOnly;
  }
 
  /**
   * Returns the number of turns after which the game ends.
   * @return the number of turns after which the game ends.
   */
  public int getGameLength() {
    return this.gameLength;
  }
 
  /**
   * Returns the node with the given ID.
   * @param nodeId the ID of the node.
   * @return the node with the given ID; returns <code>null</code>
   *       if no such node was found.
   */
  public Node getNode(String nodeId) {
    if(nodeId == null)
      throw new IllegalArgumentException("nodeId must not be null");
    return this.nodes.get(nodeId);
  }
 
  /**
   * Returns the list of nodes in the game graph.
   * @return the list of nodes in the game graph.
   */
  public Map<String, Node> getNodes() {
    return this.nodesReadOnly;
  }
 
  /**
   * Returns numbers of the turns since the beginning of the game, in
   * which the phantom is revealed to the detectives.
   * @return a set that contains the numbers of turns.
   */
  public Set<Integer> getPhantomReveals() {
    return this.phantomRevealsReadonly;
  }
 
  /**
   * Returns the list of available transport types in the game graph.
   * @return the list of available transport types in the game graph.
   */
  public Map<String, TransportType> getTransportTypes() {
    return this.transportTypesReadOnly;
  }
 
  /**
   * Returns the transport type with the given name.
   * @param name the name of the transport type.
   * @return the transport type with the given name.
   */
  public TransportType getTransportType(String name) {
    if(name == null)
      throw new IllegalArgumentException("name must not be null");
    return transportTypes.get(name);
  }
 
  /**
   * Checks if an actor is mobile.
   * @param actor The checked actor.
   * @param other List of other actors of the same kind.
   * @return <code>True</code> if the given actor can perform a move.
   */
  protected boolean isMobile(Actor actor, List<Actor> others) {
    Graph.Node currentPosition = actor.getCurrentPosition();
    assert(currentPosition != null);

    for(Graph.Edge edge : currentPosition.getOutgoingEdges()) {
      if(actor.hasTicket(edge.getTransportType())) {
        boolean canThere = true;
        Graph.Node target = edge.getTarget();

        for(Actor other : others) {
          if(other == actor) {
            continue;
          }

          if(target.equals(other.getCurrentPosition())) {
            canThere = false;
            break;
          }
        }

        if(canThere) {
          return true;
        }
      }
    }

    return false;
  }
 
  /**
   * Loads the parameters of the game (number of turns, and turns, in
   * which the phantom is revealed).
   *
   * @param gameSpec contains specification of the game.
   * @throws GraphFormatException if there is a problem with loading the
   *           parameters of the game.
   */
  protected void loadGameProperties(Properties gameSpec) throws GraphFormatException {
    String turns_str = gameSpec.getProperty(PROPERTY_GAME);
    if(turns_str == null || turns_str.isEmpty())
      throw new GraphFormatException("The number of turns of the game was not specified");
    try {
      this.gameLength = Integer.parseInt(turns_str);
      if(this.gameLength <= 0)
        throw new GraphFormatException("The number of turns must be a positive number");
    }
    catch(NumberFormatException e) {
      throw new GraphFormatException("THe number of turns is not a valid number");
    }
   
    String reveals_str = gameSpec.getProperty(PROPERTY_REVEALS);
    if(reveals_str == null || reveals_str.isEmpty())
      throw new GraphFormatException("The turns in which the phantom is revealed were not specified");
    String[] reveals = reveals_str.split("\\s+");
    for(String reveal_str : reveals) {
      reveal_str = reveal_str.trim();
      if(reveal_str.isEmpty())
        continue;
      try {
        int reveal = Integer.parseInt(reveal_str);
        this.phantomReveals.add(reveal);
      }
      catch(NumberFormatException e) {
        throw new GraphFormatException(String.format("Turn number %d is not a valid number", reveal_str));
      }
    }
  }
 
  /**
   * Loads the game graph from a list of strings. Each string specifies
   * a single object in the game graph.
   * @param records specifications of the objects in the game graph.
   * @throws GraphFormatException if there is a problem when parsing the graph.
   */
  protected void loadGraph(String[] lines) throws GraphFormatException {
    if(lines == null)
      throw new IllegalArgumentException("lines must not be null");
    Properties[] records = new Properties[lines.length];
    for(int i = 0; i < lines.length; i++) {
      try {
        records = Parser.parseLines(lines);
      }
      catch(Exception e) {
        throw new GraphFormatException(e.getMessage(), i);
      }
    }
   
    loadGraph(records);
  }
 
  /**
   * Loads the game graph from a list of records. Each record specifies
   * a single object in the game graph.
   * @param records specifications of the objects in the game graph.
   * @throws GraphFormatException if there is a problem when parsing the graph.
   */
  protected void loadGraph(Properties[] records) throws GraphFormatException {
    for(int i = 0; i < records.length; i++) {
      try {
        Properties properties = records[i];
        if(properties.containsKey(PROPERTY_EDGE)) {
          MultiEdge me = new MultiEdge(properties);
          for(Edge e : me.getEdges()) {
            e.getSource().outgoingEdges.add(e);
            e.getTarget().incomingEdges.add(e);
            edges.add(e);
          }
        }
        else if(properties.containsKey(PROPERTY_NODE)) {
          Node nd = new Node(properties);
          nodes.put(nd.getId(), nd);
        }
        else if(properties.containsKey(PROPERTY_NODES)) {
          if(!properties.containsKey(Nodes.PROPERTY_FROM)) {
            throw new GraphFormatException("Invalid specification of nodes. Missin the \"from:\" part.");
          }
          if(!properties.containsKey(Nodes.PROPERTY_TO)) {
            throw new GraphFormatException("Invalid specification of nodes. Missin the \"to:\" part.");
          }
          int start = Integer.parseInt(properties.getProperty(Nodes.PROPERTY_FROM));
          int end = Integer.parseInt(properties.getProperty(Nodes.PROPERTY_TO));

          for(int j = start; j <= end; j++) {
            String id = Integer.toString(j);
            Node nd = new Node(id);
            nodes.put(id, nd);
          }
        }
        else if(properties.containsKey(PROPERTY_TRANSPORT)) {
          TransportType transport = new TransportType(properties);
          transportTypes.put(transport.getName(), transport);
        }
        else if(properties.containsKey(PROPERTY_DETECTIVE) || properties.containsKey(PROPERTY_PHANTOM)) {
          Actor actor = new Actor();
          actor.loadProperties(this, properties, this.getTransportTypes());

          this.actors.add(actor);
        }
        else if(properties.containsKey(PROPERTY_GAME)) {
          loadGameProperties(properties);
        }
      }
      catch(Exception err) {
        throw new GraphFormatException(err.getMessage(), i);
      }
    }
   
    // Verify that there is at least one phantom, and at least one detective
    int phantoms = 0;
    int detectives = 0;
    for(Actor actor : this.actors) {
      if(actor.isDetective())
        detectives++;
      else if(actor.isPhantom())
        phantoms++;
      else
        throw new IllegalStateException("Actor is neither a detective nor a phantom: " + actor.getId());
    }
    if(phantoms == 0)
      throw new GraphFormatException("No phantom objects were found");
    if(detectives == 0)
      throw new GraphFormatException("No detective objects were found");
   
    addUniversalEdges();
  }
 
  /**
   * Move the actors according to the request.
   * @param actors Actors in this list are moved in the order they appear in the list.
   * @param message The move request.
   * @return A collection of actors which could not move.
   * @throws ProtocolException If something undesirable not according to rules is requested.
   */
  public Collection<Actor> moveActors(List<Actor> actors, MessageMoveBase message) throws ProtocolException {
    assert(!actors.isEmpty());

    List<Actor> immobile = new ArrayList<Actor>(actors.size());
    List<Actor> moved = new ArrayList<Actor>(actors.size());

    for(Actor mover : actors) {
      MessageMoveBase.ActorMove[] moves = {null, null};
      boolean didMove = false;

      for(MessageMoveBase.ActorMove m : message.getMoves()) {
        if(mover == m.getActor()) {
          if(moves[0] == null) {
            moves[0] = m;
          } else if(moves[1] == null && mover.hasDoubleMove()) {
            moves[1] = m;
          } else {
            throw new ProtocolException("Too many moves for a single actor.", null);
          }
        }
      }

      if(!isMobile(mover, actors)) {
        immobile.add(mover);
      }

      for(MessageMoveBase.ActorMove move : moves) {
        if(move == null) {
          break;
        }

        assert(mover.equals(move.getActor()));

        if(!isMobile(mover, actors)) {
          throw new ProtocolException("Actor is immobile: " + mover.getId(), null);
        }

        Graph.Node target = move.getTargetNode();
        TransportType tt = move.getTransportType();

        for(Actor a : getActors()) {
          assert(a.getCurrentPosition() != null);
          // Note: It may be possible to move back to the origin.
          if(a.getCurrentPosition() == target
              && !a.equals(mover)
              && a.isDetective() == mover.isDetective()) { // Cannot place two actors of the same type to the same node.
            String errorMessage = String.format("Cannot place actor '%s' to node '%s', node is already occupied by actor '%s'",
                mover.getId(), target.getId(), a.getId());
            throw new ProtocolException(errorMessage, null);
          }
        }

        mover.moveTo(target, tt);
        didMove = true;
      }

      if(didMove) {
        moved.add(mover);
      }
    }

    if(moved.size() + immobile.size() != actors.size()) {
      StringBuilder sb = new StringBuilder();
      for(Actor a : actors) {
        if(!moved.contains(a) && !immobile.contains(a)) {
          if(sb.length() > 0) sb.append(", ");
          sb.append(a.getId());
        }
      }
      throw new ProtocolException("Some actor did not move: " + sb, null);
    }

    return immobile;
  }
 
  /**
   * Place the actors according to the request. When using this method on
   * server, be sure to call {@link #verifyActorPlacement(MessageMoveBase, Client)}
   * beforehand.
   *
   * @param message The move request.
   * @throws ProtocolException If something undesirable not according to rules is requested.
   */
  public void placeActors(MessageMoveBase message) throws ProtocolException {
    for(MessageMoveBase.ActorMove move : message.getMoves()) {
      Actor actor = move.getActor();
      Graph.Node target = move.getTargetNode();

      for(Actor a : getActors()) {
        if(a.getCurrentPosition() != null
          && a.getCurrentPosition().equals(target)
          && a.isDetective() == actor.isDetective()) { // Cannot place two actors of the same type to the same node.
          throw new ProtocolException("Cannot place an actor to an already full node.", null);
        }
      }

      actor.setCurrentPosition(target);
    }
  }
 
  /**
   * Verifies that the actor placement message is valid. Checks that exactly all
   * actors of the client are placed.
   * 
   * @param message the placement message.
   * @param client the client that sent the message.
   * @throws ProtocolException if the placement is not valid.
   */
  public void verifyActorPlacement(MessageMoveBase message, Client client) throws ProtocolException {
    if(message == null)
      throw new IllegalArgumentException("message must not be null");
    if(client == null)
      throw new IllegalArgumentException("client must not be null");
   
    HashSet<Actor> placedActors = new HashSet<Actor>();
    HashSet<Node> usedNodes = new HashSet<Node>();
    HashSet<Actor> allActors = new HashSet<Actor>(getActors(client.getClientType()));

    for(ActorMove move : message.getMoves()) {
      Actor actor = move.getActor();
      if(actor.getCurrentPosition() != null)
        throw new ProtocolException("Position was already assigned to actor: " + actor.getId(), client);
      if(placedActors.contains(actor))
        throw new ProtocolException("Position was specified multiple times to actor: " + actor.getId(), client);
      if(!allActors.contains(actor))
        throw new ProtocolException("Client placed other player's actor: " + actor.getId(), client);
      placedActors.add(actor);
      if(move.getTargetNode() == null)
        throw new ProtocolException("The target node was not specified for actor: " + actor.getId(), client);
      if(move.getTargetNode().isOccupied(client.getClientType()))
        throw new ProtocolException(String.format("Actor %s cannot be placed to node %s, the node is already occupied",
                      actor.getId(), move.getTargetNode().getId()),
                client);
      if(move.getTransportType() != null)
        throw new ProtocolException("Transport type was specified in the placement message for actor: " + actor.getId(), client);
      if(usedNodes.contains(move.getTargetNode()))
        throw new ProtocolException(String.format("Actor %s cannot be placed to node %s, the node is already occupied",
                            actor.getId(), move.getTargetNode().getId()),
                      client);
      usedNodes.add(move.getTargetNode());
    }
   
    for(Actor actor : allActors) {
      if(!placedActors.contains(actor))
        throw new ProtocolException("Actor was not placed: " + actor.getId(), client);
    }
  }
 
  /**
   * Updates positions and tickets according to the 'update'
   * message. This method should never be used on the server, as it does not check
   * that the moves are valid, only updates positions of the actors and their tickets.
   *
   * @param message the message, according to which the actors are updated.
   */
  public void updateActors(MessageMoveBase message) {
    for(MessageMoveBase.ActorMove move : message.getMoves()) {
      Actor actor = move.getActor();
      Graph.Node target = move.getTargetNode();
      TransportType transport = move.getTransportType();
     
      // Update position of the actor
      actor.setCurrentPosition(null);
      if(target != null)
        actor.setCurrentPosition(target);
      // Remove one ticket for the used transport type
      if(transport != null)
        actor.addTickets(transport, -1);
    }
   
    // If the message is MessageUpdate, also update the numbers of tickets
    // and captured phantoms
    if(message instanceof MessageUpdate) {
      MessageUpdate msgUpdate = (MessageUpdate)message;
      for(Actor phantom : msgUpdate.getCapturedPhantoms())
        phantom.capture();
     
      for(ActorTickets tickets : msgUpdate.getActorTickets()) {
        Actor actor = tickets.getActor();
        for(Entry<TransportType, Integer> ttype : tickets.getTickets().entrySet()) {
          actor.setNumberOfTickets(ttype.getKey(), ttype.getValue());
        }
      }
    }
  }

  /**
   * Serializes the graph to the same format, as the one, from which the graph
   * is loaded.
   *
   * @return serialized version of the graph.
   */
  public Properties[] serialize() {
    Properties[] properties = new Properties[getNodes().size() + getEdges().size() + getActors().size() + getTransportTypes().size() + 1];
    int pos = 0;
   
    Properties game = new Properties();
    game.setProperty(PROPERTY_GAME, Integer.toString(getGameLength()));
    StringBuilder reveals_str = new StringBuilder();
    for(Integer reveal : phantomReveals) {
      if(reveals_str.length() > 0)
        reveals_str.append(' ');
      reveals_str.append(reveal.intValue());
    }
    game.setProperty(PROPERTY_REVEALS, reveals_str.toString());
    properties[pos++] = game;
   
    for(TransportType transport : transportTypes.values()) {
      properties[pos++] = transport.serialize();
    }
    for(Node nd : getNodes().values()) {
      properties[pos++] = nd.serialize();
    }
    for(Edge e : getEdges()) {
      properties[pos++] = e.serialize();
    }
    for(Actor a : getActors()) {
      properties[pos++] = a.serialize();
    }
       
    return properties;
  }

  /**
   * Creates a new empty game graph.
   */
  public Graph() {
    this.init();
  }

  /**
   * Creates a graph using the given specification.
   * @param lines specifications of the objects in the game graph.
   * @throws GraphFormatException if there is a problem when parsing the graph.
   */
  public Graph(String[] lines) throws GraphFormatException {
    this.init();
    this.loadGraph(lines);
  }
 
  /**
   * Creates a graph from the given specification.
   * @param records specification of the objects in the game graph.
   * @throws GraphFormatException if there is a problem when parsing the graph.
   */
  public Graph(Properties[] records) throws GraphFormatException {
    this.init();
    this.loadGraph(records);
  }

  /**
   * Creates a new graph from a specification in a file.
   *
   * @param graphFile the file, from which the graph is loaded.
   * @throws IOException if there is a problem with parsing the graph.
   * @throws ParserException if the format of the graph is not valid.
   */
  public Graph(File graphFile) throws IOException, ParserException {
    this.init();
    Properties[] records = Parser.parse(graphFile);
    loadGraph(records);
  }

  protected void init() {
    this.actors = new ArrayList<Actor>();
    this.actorsReadOnly = Collections.unmodifiableList(this.actors);
    this.edges = new ArrayList<Edge>();
    this.edgesReadOnly = Collections.unmodifiableList(this.edges);
    this.nodes = new HashMap<String, Node>();
    this.nodesReadOnly = Collections.unmodifiableMap(this.nodes);
    this.transportTypes = new HashMap<String, TransportType>();
    this.transportTypesReadOnly = Collections.unmodifiableMap(this.transportTypes);
    this.phantomReveals = new HashSet<Integer>();
    this.phantomRevealsReadonly = Collections.unmodifiableSet(this.phantomReveals);
   
    try {
      this.transportTypes.put(PROPERTY_UNIVERSAL_TICKET,
                  new TransportType(PROPERTY_UNIVERSAL_TICKET, TransportType.USER_ANY));
    }
    catch(GraphFormatException e) {
      // This should never happen
      throw new IllegalStateException("Could not create the 'universal' transport type");
    }
  }
}
TOP

Related Classes of cz.matfyz.aai.fantom.game.Graph$Edge

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.