Package cz.matfyz.aai.fantom.game

Source Code of cz.matfyz.aai.fantom.game.Actor$PositionChange

/*
   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.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;

import cz.matfyz.aai.fantom.game.Graph.Edge;
import cz.matfyz.aai.fantom.game.Graph.Node;
import cz.matfyz.aai.fantom.message.ClientType;
import cz.matfyz.aai.fantom.message.MessageMoveBase.ActorMove;
import cz.matfyz.aai.fantom.server.ProtocolException;

/**
* Represents a single actor (detective, phantom) on the game board.
* Each actor is characterized by its current position, and the transport
* tickets available to it.
*/
public class Actor {
  /**
   * Records the change in position of this actor.
   */
  public class PositionChange {
    /**
     * The node, where the actor started.
     */
    private Node sourceNode;
   
    /**
     * The node, to which the actor moved.
     */
    private Node targetNode;
   
    /**
     * The transport type used for the movement.
     */
    private TransportType transportType;
   
    /**
     * Returns the actor, for which this change was recorded.
     * @return the actor, for which this change was recorded.
     */
    public Actor getActor() {
      return Actor.this;
    }
   
    /**
     * Returns the node, from which the actor moved.
     * @return the node, from which the actor moved.
     */
    public Node getSourceNode() {
      return this.sourceNode;
    }
   
    /**
     * Returns the node, to which the actor moved.
     * @return the node, to which the actor moved.
     */
    public Node getTragetNode() {
      return this.targetNode;
    }
   
    /**
     * Returns the transport type used for the movement.
     * @return the transport type used for the movement.
     */
    public TransportType getTransportType() {
      return this.transportType;
    }
   
    /**
     * Initializes a new change in position of the actor.
     * @param sourceNode the node, from which the agent moved.
     * @param targetNode the node, to which the agent moved.
     * @param trasnportType the type of transport used by the agent.
     */
    public PositionChange(Node sourceNode, Node targetNode, TransportType transportType) {
      this.sourceNode = sourceNode;
      this.targetNode = targetNode;
      this.transportType = transportType;
    }
  }
 
  /**
   * The current position of the actor on the game board (i. e. the
   * node on which the actor stands).
   */
  private Graph.Node currentPosition;
 
  /**
   * The number of double move tickets available to the actor.
   */
  private int doubleMoves;
   
  /**
   * The numbers of tickets available to the actor.
   */
  private Map<TransportType, Integer> tickets;
  private Map<TransportType, Integer> ticketsReadOnly;

  /**
   * An ID of this actor.
   */
  private String id;
 
  /**
   * Specifies, if the actor is a phantom..
   */
  private boolean isPhantom = false;
 
  /**
   * Only applies to the phantom. Set to <code>true</code>, if the phantom
   * was captured by the detectives.
   */
  private boolean isCaptured = false;
 
  /**
   * Checks if the actor can move from <code>source</code> to
   * <code>destination</code> in one turn (i.e. by using a single edge,
   * or by using a double move).
   *
   * @param source the node where the actor starts.
   * @param destination the node where the actor ends.
   * @return <code>true</code> if the actor can move to <code>destination</code>
   *       from <code>source</code>; otherwise, <code>false</code>.
   */
  public boolean canMove(Graph.Node source, Graph.Node destination) {
    return canMoveSingleEdge(source, destination)
      || canMoveDoubleMove(source, destination);
  }
 
  /**
   * Checks if the actor can move from <code>source</code> to
   * <code>destination</code> using double move.
   *
   * @param source the node where the actor starts.
   * @param destination the node where the actor ends.
   * @return <code>true</code> if the actor can move to <code>destination</code>
   *       from <code>source</code> using a double move; otherwise,
   *       <code>false</code>.
   */
  public boolean canMoveDoubleMove(Graph.Node source, Graph.Node destination) {
    if(getDoubleMoves() == 0)
      return false;
    if(destination.isOccupied(getClientType()) && !destination.equals(getCurrentPosition()))
      return false;
    for(Graph.Edge e : source.getOutgoingEdges()) {
      if(hasTickets(e.getTransportType(), 1)
          && (!e.getTarget().isOccupied(getClientType())
            || e.getTarget().equals(getCurrentPosition()))) {
        for(Graph.Edge e2 : e.getTarget().getOutgoingEdges()) {
          if(e2.getTarget() == destination) {
            if(hasTicket(e.getTransportType(), e2.getTransportType()))
              return true;
          }
        }
      }
    }
    return false;
  }
 
  /**
   * Checks if the actor can move from <code>source</code> to
   * <code>destination</code> using a single edge.
   *
   * @param source the node where the actor starts.
   * @param destination the node, where the actor ends.
   * @return <code>true</code> if the actor can move to <code>destination</code>
   *       from <code>source</code> using a single edge; otherwise,
   *       <code>false</code>.
   */
  public boolean canMoveSingleEdge(Graph.Node source, Graph.Node destination) {
    return canMoveSingleEdge(source, destination, null);
  }

  /**
   * Checks if the actor can move from <code>source</code> to
   * <code>destination</code> using a single edge and the given transport type.
   *
   * @param source the node where the actor starts.
   * @param destination the node, where the actor ends.
   * @param transport the requested transport type. if null, the transport type is not specified and any can be used
   * @return <code>true</code> if the actor can move to <code>destination</code>
   *       from <code>source</code> using a single edge; otherwise,
   *       <code>false</code>.
   */
  public boolean canMoveSingleEdge(Graph.Node source, Graph.Node destination, TransportType transport) {
    if(destination == null)
      throw new IllegalArgumentException("destination must not be null");
    if(source == null)
      throw new IllegalArgumentException("source must not be null");
   
    if(destination.isOccupied(getClientType())
        && !destination.equals(getCurrentPosition()))
      return false;
    for(Graph.Edge e : source.getOutgoingEdges()) {
      if(e.getTarget() == destination
          && hasTicket(e.getTransportType())
          && (transport == null || e.getTransportType().equals(transport)))
        return true;
    }

    return false;
  }
 
  /**
   * Determines, if the actor can move from its current position to the
   * given node in the graph.
   * @param destination the node in the graph, for which the check is
   *         performed.
   * @return <code>true</code> if the actor can move to <code>destination</code>;
   *         otherwise, <code>false</code>.
   */
  public boolean canMoveTo(Graph.Node destination) {
    if(currentPosition == null)
      throw new IllegalStateException("the current position of the actor is not defined");
   
    return canMove(currentPosition, destination);
  }
 
  /**
   * Determines, if the actor can move from its current position to the
   * given node in the graph using the given transport type.
   * @param destination the node in the graph, for which the check is
   *         performed.
   * @param transport the desired transport type
   * @return <code>true</code> if the actor can move to <code>destination</code> using the transport type;
   *         otherwise, <code>false</code>.
   */
  public boolean canMoveTo(Graph.Node destination, TransportType transport) {
    if(currentPosition == null)
      throw new IllegalStateException("the current position of the actor is not defined");
   
    return canMoveSingleEdge(currentPosition, destination, transport);
  }
 
  /**
   * Sets the 'captured' flag for this actor. This method can only be called
   * for phantoms.
   *
   * @throws IllegalStateException if the method is called for a detective.
   */
  public void capture() {
    if(!isPhantom)
      throw new IllegalStateException("Only phantoms can be captures");
    isCaptured = true;
  }
 
  /**
   * Compares the actor object with another actor for equality. Two actor
   * objects are equal, if they have the same ID, and the same numbers of tickets.
   */
  @Override
  public boolean equals(Object otherObject) {
    if(!(otherObject instanceof Actor))
      return false;
    Actor other = (Actor)otherObject;
    if (!other.getId().equals(this.getId())
        || other.getDoubleMoves() != this.getDoubleMoves()
        || other.getClientType() != this.getClientType()
        || other.isCaptured() != this.isCaptured())
      return false;
    if((this.getCurrentPosition() == null && other.getCurrentPosition() != null)
        || (this.getCurrentPosition() != null && !this.getCurrentPosition().equals(other.getCurrentPosition())))
      return false;
   
    for(TransportType transport : other.getTickets().keySet()) {
      if(other.getNumberOfTickets(transport) != this.getNumberOfTickets(transport))
        return false;
    }
    for(TransportType transport : this.getTickets().keySet()) {
      if(other.getNumberOfTickets(transport) != this.getNumberOfTickets(transport))
        return false;
    }
    return true;
  }

  /**
   * Returns the type of client to which the actor belongs.
   * @return the type of client to which the actor belongs.
   */
  public ClientType getClientType() {
    if(isPhantom)
      return ClientType.PHANTOM;
    else
      return ClientType.DETECTIVE;
  }
 
  /**
   * Returns the current position of the actor.
   * @return the current position of the actor.
   */
  public Graph.Node getCurrentPosition() {
    return currentPosition;
  }

  /**
   * Sets the current position.
   * @param position The assigned position of the actor.
   */
  public void setCurrentPosition(Graph.Node position) {
    this.currentPosition = position;
  }

  /**
   * Returns the ID of the actor.
   * @return the ID of the actor.
   */
  public String getId() {
    if(this.id == null)
      throw new IllegalStateException("ID of an actor is not set.");
    return this.id;
  }
 
  /**
   * Returns the number of double move tickets available to the actor.
   * @return the number of double move tickets available to the actor.
   */
  public int getDoubleMoves() {
    return doubleMoves;
  }

  /**
   * Returns true if this actor has a double-move ticket.
   * @return true if this actor has a double-move ticket.
   */
  public boolean hasDoubleMove() {
    return getDoubleMoves() > 0;
  }
 
  /**
   * Returns the list of single edge moves for this actor at the current
   * state of the game. Takes available tickets into account, but does not
   * consider positions of other actors (as they may change in the same
   * movement message).
   *
   * @param graph the graph, for which the movements are determined.
   * @return the list of legal moves;
   */
  public List<ActorMove> getLegalMoves(Graph graph) {
    ArrayList<ActorMove> moves = new ArrayList<ActorMove>();
    if(isCaptured())
      return moves;
    if(currentPosition == null) {
      for(Node n : graph.getNodes().values())
        moves.add(new ActorMove(this, n, null));
    }
    else {
      for(Edge e : currentPosition.getOutgoingEdges()) {
        if(hasTicket(e.getTransportType())) {
          moves.add(new ActorMove(this, e.getTarget(), e.getTransportType()));
        }
      }
    }
    return moves;
  }
 
  /**
   * Returns true, if this actor is a phantom, and it was already captured
   * by the detectives.
   * @return true, if this actor is a phantom that was already captured.
   */
  public boolean isCaptured() {
    return isCaptured;
  }
 
  /**
   * Returns the number of tickets of the given type available to the
   * actor.
   * @param transport the transport type, for which the number of
   *         tickets is returned.
   * @return the number of tickets of the given type available to the actor.
   */
  public int getNumberOfTickets(TransportType transport) {
    if(transport == null)
      throw new IllegalArgumentException("transport must not be null");
    Integer ticketCount = tickets.get(transport);
    if(ticketCount != null)
      return ticketCount.intValue();
    return 0;
  }

  /**
   * Sets the number of tickets of the given type available to the
   * actor.
   * @param transport the transport type, for which the number of
   *         tickets is set.
   * @param count the set value
   */
  public void setNumberOfTickets(TransportType transport, int count) {
    if( (this.isDetective() && !transport.isUsedByDetective())
      || (this.isPhantom() && !transport.isUsedByPhantom()) ) {
      throw new IllegalArgumentException("setting tickets which are not allowed");
    }
    tickets.put(transport, count);
  }

  /**
   * Adds the given number of tickets of the given type.
   * @param transport the transport type, for which the number of
   *         tickets is added.
   * @param count the set value
   */
  public void addTickets(TransportType transport, int count) {
    setNumberOfTickets(transport, count + getNumberOfTickets(transport));
  }

  /**
   * Adds one ticket of the given type.
   * @param transport the transport type, for which the number of
   *         tickets is added.
   * @param count the set value
   */
  public void addTicket(TransportType transport) {
    addTickets(transport, 1);
  }
 
  /**
   * Returns the number of tickets available to the actor.
   * @return the number of tickets available to the actor.
   */
  public Map<TransportType, Integer> getTickets() {
    return ticketsReadOnly;
  }
 
  @Override
  public int hashCode() {
    return id.hashCode();
  }
 
  /**
   * Checks if the player can use the specified transport type (i.e.
   * has ticket of the given type, or an universal ticket).
   *
   * @param transport the transport type.
   * @return <code>true</code> if the player can use this transport type;
   *       otherwise, <code>false</code>.
   */
  public boolean hasTicket(TransportType transport) {
    return hasTickets(transport, 1);
  }
 
  /**
   * Checks if the player can use the specified transport types (i.e.
   * has the tickets for the given transport types, or enough universal
   * tickets).
   *
   * @param transport1 the first type of transport.
   * @param transport2 the second type of transport.
   * @return <code>true</code> if the player can use these transport types;
   *       otherwise, <code>false</code>.
   */
  public boolean hasTicket(TransportType transport1, TransportType transport2) {
    return hasTickets(transport1, 1, transport2, 1);
  }
 
  /**
   * Checks if the player has the required number of tickets available.
   * Uses the universal tickets, if necessary.
   *
   * @param transport the type of ticket.
   * @param count the requested number of tickets.
   * @return <code>true</code>, if the requested number of tickets is
   *       available; otherwise, <code>false</code>.
   */
  public boolean hasTickets(TransportType transport, int count) {
    return count <= getNumberOfTickets(transport);
  }
 
  /**
   * Checks if the player has the required number of tickets available.
   * Uses the universal tickets, if necessary. This is version for the
   * double moves.
   *
   * @param transport the type of ticket.
   * @param count the requested number of tickets.
   * @return <code>true</code>, if the requested number of tickets is
   *       available; otherwise, <code>false</code>.
   */
  public boolean hasTickets(TransportType transport1, int count1, TransportType transport2, int count2) {
    if(transport1 == transport2)
      return hasTickets(transport1, count1 + count2);
    else {
      return count1 <= getNumberOfTickets(transport1)
        && count2 <= getNumberOfTickets(transport2);
    }
  }
 
  /**
   * Checks if the actor is hidden to other players.
   * @return <code>true</code> if the actor is hidden to other players;
   *       otherwise, <code>false</code>.
   */
  public boolean isHidden() {
    return isPhantom();
  }

  /**
   * Checks if this actor is a phantom.
   * @return <code>true</code> if this actor is a phantom.
   */
  public boolean isPhantom() {
    return this.isPhantom;
  }

  /**
   * Checks if this actor is a detective.
   * @return <code>true</code> if this actor is a detective.
   */
  public boolean isDetective() {
    return !this.isPhantom;
  }

  /**
   * Loads the information about the edge from <code>properties</code>.
   * @param properties The specification of the edge.
   * @param transportTypes All available transport types. Mapping from names to types.
   * @throws GraphFormatException If there is a problem when processing the properties.
   */
  protected void loadProperties(Graph graph, Properties properties, Map<String, TransportType> transportTypes) throws GraphFormatException {
    String whoami = null;

    for(String property : properties.stringPropertyNames()) {
      if(property.equals(Graph.PROPERTY_DETECTIVE) || property.equals(Graph.PROPERTY_PHANTOM)) {
        whoami = property;
        this.id = properties.getProperty(property);
        this.isPhantom = property.equals(Graph.PROPERTY_PHANTOM);
      } else if(property.equals(Graph.PROPERTY_DOUBLE_TICKET)) {
        this.doubleMoves = Integer.parseInt(properties.getProperty(property));
      } else if(property.equals(Graph.PROPERTY_POSITION)) {
        this.currentPosition = graph.getNode(properties.getProperty(property));
      } else {
        TransportType transport = transportTypes.get(property);
        if(transport == null) {
          throw new GraphFormatException("Unknown transport type: " + property);
        }
        Integer cnt = Integer.valueOf(properties.getProperty(property));
        this.tickets.put(transport, cnt);
      }
    }

    if(whoami == null) {
      throw new GraphFormatException("Unknown actor type.");
    }
  }
 
  /**
   * Performs a move of the actor to the given node, using the given transport
   * type.
   * @param target the target node for the movement.
   * @param transport the transport type used for the movement.
   * @throws ProtocolException if the actor cannot perform such move.
   * @returns information about the movement of the actor.
   */
  public PositionChange moveTo(Node target, TransportType transport) throws ProtocolException {
    Node origin = getCurrentPosition();
    if(!canMoveTo(target, transport))
      throw new ProtocolException("Cannot perform the requested move", null);
   
    // Use one ticket for the selected transport type
    addTickets(transport, -1);
    // Update position of the actor
    this.currentPosition = target;
    return new PositionChange(origin, target, transport);
  }
 
  /**
   * Undoes a move, previously performed by the actor.
   * @param change information about the movement to be undone.
   */
  public void undoMove(PositionChange change) {
    if(change == null)
      throw new IllegalArgumentException("Change must not be null");
    if(change.getActor() != this)
      throw new IllegalArgumentException("The change object was created for a different actor");
    if(getCurrentPosition() != change.getTragetNode())
      throw new IllegalArgumentException("Cannot undo the change, the actor is at a different position");
   
    this.currentPosition = change.getSourceNode();
    addTickets(change.getTransportType(), 1);
  }
 
  /**
   * Serializes the actor to the format, from which it was loaded.
   * @return the specification of the actor.
   */
  public Properties serialize() {
    Properties res = new Properties();
   
    if(getCurrentPosition() != null)
      res.setProperty(Graph.PROPERTY_POSITION, getCurrentPosition().getId());
    res.setProperty(isPhantom ? Graph.PROPERTY_PHANTOM : Graph.PROPERTY_DETECTIVE, getId());
    res.setProperty(Graph.PROPERTY_DOUBLE_TICKET, Integer.toString(getDoubleMoves()));
   
    for(Entry<TransportType, Integer> ticket : tickets.entrySet()) {
      res.setProperty(ticket.getKey().getName(), ticket.getValue().toString());
   
   
    return res;
  }

  /**
   * Creates a new actor on the given position in the graph.
   *
   */
  public Actor() {
    this.tickets = new HashMap<TransportType, Integer>();
    this.ticketsReadOnly = Collections.unmodifiableMap(this.tickets);
  }
}
TOP

Related Classes of cz.matfyz.aai.fantom.game.Actor$PositionChange

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.