Package cz.matfyz.aai.fantom.server

Source Code of cz.matfyz.aai.fantom.server.Server

/*
   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.server;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.Random;

import org.apache.log4j.Logger;

import cz.matfyz.aai.fantom.game.Actor;
import cz.matfyz.aai.fantom.game.Graph;
import cz.matfyz.aai.fantom.game.TransportType;
import cz.matfyz.aai.fantom.message.ClientType;
import cz.matfyz.aai.fantom.message.Message;
import cz.matfyz.aai.fantom.message.MessageMove;
import cz.matfyz.aai.fantom.message.MessageMoveBase;
import cz.matfyz.aai.fantom.message.MessageQuit;
import cz.matfyz.aai.fantom.message.MessageStart;
import cz.matfyz.aai.fantom.message.MessageUpdate;
import cz.matfyz.aai.fantom.message.MessageMoveBase.ActorMove;
import cz.matfyz.aai.fantom.utils.ParserException;

/**
* The game server. Handles communication between the clients, maintains
* its current state and produces full logs of the game.
*/
public class Server {
  /**
   * The name of the logger used by the server.
   */
  protected static final String LOGGER_NAME = "cz.matfyz.aai.fantom.server";
 
  /**
   * The name of the file that contains configuration of the server.
   */
  protected static final String CONFIGURATION_NAME = "fantom.properties";
 
  /**
   * The name of the property in the file {@link #CONFIGURATION_NAME} that
   * contains the initial value of the seed of the random number generator.
   */
  protected static final String CONFIGURATION_PROPERTY_SEED = "fantom.server.seed";
 
  /**
   * The name of the property in the file {@link #CONFIGURATION_NAME} that
   * specifies if the messages between the server and the clients should be
   * logged to a file.
   */
  protected static final String CONFIGURATION_PROPERTY_LOG_MESSAGES = "fantom.server.log.messages";
 
  /**
   * The logger used by the server.
   */
  protected Logger logger = Logger.getLogger(LOGGER_NAME);
 
  /**
   * The client that moves the detectives.
   */
  protected Client detectiveClient;
  /**
   * The command-line to start the detective client.
   */
  protected String[] detectiveCommandLine;
  /**
   * The number of games won by the detectives.
   */
  protected int detectiveWins;
 
  /**
   * The client that moves the phantom.
   */
  protected Client phantomClient;
  /**
   * The command-line to start the phantom client.
   */
  protected String[] phantomCommandLine;
  /**
   * The number of games won by the phantom.
   */
  protected int phantomWins;
 
  /**
   * The game graph used in the game. May be set to <code>null</code>,
   * when no game is active.
   */
  protected Graph graph;
 
  /**
   * Set to <code>true</code> if the client should log messages going
   * between the client and the server.
   */
  protected boolean logMessages;
 
  /**
   * The game graph as it was loaded from the source file. This field
   * only contains the original graph, the working copy for the game
   * is stored in {@link #graph}.
   */
  protected Graph sourceGraph;
 
  /**
   * The number of games the clients should play on the graph.
   */
  protected int gameCount;
 
  /**
   * The name of the run. This name is used to prefix all log files
   * created by the server.
   */
  protected String runName;

  /**
   * Source of non-determinism. The generator is replaced by a new
   * instance, if a seed is provided in the configuration.
   */
  protected Random rnd = new Random();
 
  /**
   * Returns the name of this run.
   * @return the name of this run.
   */
  public String getRunName() {
    return this.runName;
  }
 
  /**
   * Initializes the clients. Starts the client processes and reads the
   * message with their names.
   *
   * @throws ServerException if there is a problem with starting the clients.
   */
  protected void initClients() throws ServerException {
    logger.info("Creating the detective client");
    detectiveClient = new Client(detectiveCommandLine, ClientType.DETECTIVE, getRunName(), logMessages);
    logger.info("Creating the phantom client");
    phantomClient = new Client(phantomCommandLine, ClientType.PHANTOM, getRunName(), logMessages);
   
    // Start the detective client, wait for the initial message
    logger.info("Starting the detective client process");
    detectiveClient.startProcess();
    logger.info("Detective client process started");
   
    // Start the phantom client, wait for the initial message
    logger.info("Starting the phantom client process");
    phantomClient.startProcess();
    logger.info("Phantom process started");
  }
 
  /**
   * Kills the client processes. This method should only be used in case
   * that the server is terminated unexpectedly.
   */
  public void killClientProcesses() {
    if(detectiveClient != null)
      detectiveClient.killProcess();
    if(phantomClient != null)
      phantomClient.killProcess();
  }

  /** Starts a game.
   * @param phantoms List of phantoms in the game.
   * @param detectives List of detectives still in the game.
   * @return A winner if game is over, or <code>null</code> if nobody won during the initial phase.
   */
  protected MessageUpdate startGame(List<Actor> phantoms, List<Actor> detective) throws ServerException {
    logger.info("Initializing a new game");
    MessageStart msgStart = new MessageStart(graph);
   
    logger.debug("Sending message 'start' to the detectives");
    detectiveClient.sendMessage(msgStart);
    logger.trace("Message sent");
   
    logger.debug("Sending message 'start' to the phantom");
    phantomClient.sendMessage(msgStart);
    logger.trace("Message sent");
   
    // Place the detectives.
    logger.trace("Receiving actor placement from the detectives");
    Message msg = detectiveClient.receiveMessage(graph);
    if(!(msg instanceof MessageMove))
      throw new ProtocolException("'move' message was expected from the detective client", detectiveClient);
    logger.debug("Message received");
   
    MessageMove detectivePlacements = (MessageMove) msg;
    try {
      logger.trace("Placing detectives on the graph");
      graph.verifyActorPlacement(detectivePlacements, detectiveClient);
      graph.placeActors(detectivePlacements);
      logActorPlacement(detectiveClient, detectivePlacements);
      logger.trace("Detectives were placed");
    }
    catch(ClientException e) {
      e.setClient(detectiveClient);
      throw e;
    }

    // Inform the phantom about the initial placement of the detectives.
    logger.debug("Sending information about detective placement to the phantom");
    msg = new MessageUpdate(graph, detectivePlacements.getMoves(), null);
    phantomClient.sendMessage(msg);
    logger.trace("Message was sent");

    // Place the phantom.
    logger.debug("Receiving actor placement from the phantom");
    msg = phantomClient.receiveMessage(graph);
    if(!(msg instanceof MessageMove))
      throw new ProtocolException("'move' message was expected from the detective client", phantomClient);
    logger.trace("Message received");
   
    MessageMove phantomPlacement = (MessageMove) msg;
    try {
      logger.debug("Placing phantoms on the graph");
      graph.verifyActorPlacement(phantomPlacement, phantomClient);
      graph.placeActors(phantomPlacement);
      logActorPlacement(phantomClient, phantomPlacement);
      logger.trace("Phantoms were placed");
    }
    catch(ClientException e) {
      e.setClient(phantomClient);
      throw e;
    }

    processCapturedPhantoms(phantoms);
    if(phantoms.isEmpty()) {
      logger.info("All phantoms were captured, detectives won the game");
      return new MessageUpdate(graph, phantomPlacement.getMoves(), ClientType.DETECTIVE);
    }

    logger.debug("Letting the phantom start.");
    msg = new MessageUpdate(graph, new ActorMove[0], null);
    phantomClient.sendMessage(msg);
    logger.trace("Phantom starts playing.");

    return null;
  }
 
  /**
   * Updates the list of phantoms according to the phantoms captured in the
   * current round.
   * @param phantoms the list of phantoms to be updated.
   */
  protected void processCapturedPhantoms(List<Actor> phantoms) {
    for(Actor phantom : graph.capturedPhantoms()) {
      logger.info(String.format("Phantom %s was captured at node %s", phantom.getId(), phantom.getCurrentPosition().getId()));
      phantom.capture();
      phantoms.remove(phantom);
    }   
  }

  /** Runs a single round of a game.
   * @param phantoms List of phantoms in the game.
   * @param detectives List of detectives still in the game.
   * @param round Current round.
   * @return A winner if game is over, or <code>null</code> if nobody won in this round.
   */
  protected MessageUpdate runRound(List<Actor> phantoms, List<Actor> detectives, int round) throws ServerException {
    logger.info(String.format("Playing round %d.", round));
    // Get the phantom moves.
   
    logger.debug("Reading movement information from the phantom");
    Message msg = phantomClient.receiveMessage(graph);
    if(!(msg instanceof MessageMove))
      throw new ProtocolException("'move' message was expected from the detective client", phantomClient);
    logger.trace("Movement information from the phantom was received");
    MessageMove phantomMove = (MessageMove) msg;
    Collection<Actor> immobilePhantoms = null;
    try {
      logActorMovement(phantomClient, phantomMove);
      logger.debug("Updating actor positions in the graph");
      immobilePhantoms = graph.moveActors(phantoms, phantomMove);
      logger.trace("Actor positions were updated");
    }
    catch(ProtocolException e) {
      e.setClient(phantomClient);
      throw e;
    }
   
    // Mark all phantoms captured in this turn as captured, and remove them from the
    // list of active phantoms.
    logger.debug("Processing captured phantoms");
    processCapturedPhantoms(phantoms);
   
    // Inform the detectives.
    logger.debug("Sending detectives information about the moves of the phantom");
    MessageMoveBase.ActorMove[] pams = phantomMove.getMoves();
    if(!graph.getPhantomReveals().contains(round)) {
      // Hide the target.
      MessageMoveBase.ActorMove[] hpams = new MessageMoveBase.ActorMove[pams.length];
      for(int i = 0; i < pams.length; i++) {
        hpams[i] = new MessageMoveBase.ActorMove(pams[i].getActor(), null, pams[i].getTransportType());
      }
      pams = hpams;
    } else {
      if(immobilePhantoms.size() > 0) {
        // There are immobile phantoms. We need to add them to the message to reveal
        // their position.
        MessageMoveBase.ActorMove[] moves = new MessageMoveBase.ActorMove[pams.length + immobilePhantoms.size()];
        int pos = 0;
        for(int i = 0; i < pams.length; i++) {
          moves[pos++] = pams[i];
        }
        for(Actor phantom : immobilePhantoms) {
          moves[pos++] = new ActorMove(phantom, phantom.getCurrentPosition(), null);
        }
        pams = moves;
      }
    }
    if(phantoms.isEmpty()) {
      return new MessageUpdate(graph, pams, ClientType.DETECTIVE);
    }
    msg = new MessageUpdate(graph, pams, null);
    detectiveClient.sendMessage(msg);
    logger.trace("Message was sent");

    // Get the moves of detectives.
    logger.debug("Reading movement information from detectives");
    msg = detectiveClient.receiveMessage(graph);
    if(!(msg instanceof MessageMove))
      throw new ProtocolException("'move' message was expected from the detective client", detectiveClient);
    logger.trace("Movement information from the phantom was received");
    MessageMove detectiveMoves = (MessageMove) msg;
    try {
      logActorMovement(detectiveClient, detectiveMoves);
      logger.debug("Updating actor positions in the graph");
      graph.moveActors(detectives, detectiveMoves);
      logger.trace("Actor positions were updated");
    }
    catch(ProtocolException e) {
      e.setClient(detectiveClient);
      throw e;
    }
   
    // First process phantoms captured in the last move...
    logger.debug("Processing captured phantoms");
    processCapturedPhantoms(phantoms);
    if(phantoms.isEmpty()) {
      //return ClientType.DETECTIVE;
      return new MessageUpdate(graph, detectiveMoves.getMoves(), ClientType.DETECTIVE);
    }

    logger.debug("Distributing tickets used by detectives to phantoms");
    for(MessageMove.ActorMove m : detectiveMoves.getMoves()) {
      TransportType transport = m.getTransportType();

      if(transport.isUsedByPhantom()) {
        int lucky = rnd.nextInt(phantoms.size()); //TODO: With multiple phantoms, how should the tickets be distributed?
        Actor luckyActor = phantoms.get(lucky);
        logger.info(String.format("Phantom %s gets one ticket for transport %s", luckyActor.getId(), transport.getName()));
        phantoms.get(lucky).addTicket(transport);
      }
    }

    // Inform the phantom.
    logger.debug("Sending phantom information about movement of the detectives");
    ClientType winner = null;
    // If this is the last round, the phantom escaped and won the game
    if(round == graph.getGameLength()) {
      logger.info("Detectives did not catch the phantoms, phantoms won the game");
      winner = ClientType.PHANTOM;
      return new MessageUpdate(graph, detectiveMoves.getMoves(), winner);
    }
   
    MessageUpdate detectiveUpdate = new MessageUpdate(graph, detectiveMoves.getMoves(), winner);
    phantomClient.sendMessage(detectiveUpdate);
    logger.trace("Message was sent");

    logger.debug("Nobody won the current round");
    return null;
  }
 
  /**
   * Runs a single game.
   * @return Returns a winner of the game.
   * @throws ServerException In case of server errors.
   * @throws ProtocolException In case some violation of rules is detected.
   */
  public MessageUpdate runGame() throws ServerException {
    this.graph = (Graph)sourceGraph.clone();
   
    List<Actor> phantoms = new ArrayList<Actor>(graph.getActors().size());
    List<Actor> detectives = new ArrayList<Actor>(graph.getActors().size());

    for(Actor a : graph.getActors()) {
      if(a.isDetective()) {
        detectives.add(a);
      } else {
        phantoms.add(a);
      }
    }

    MessageUpdate result = startGame(phantoms, detectives);
    if(result != null) {
      return result;
    }

    for(int round = 1; round <= graph.getGameLength(); round++) {
      result = runRound(phantoms, detectives, round);
      logger.info(String.format("Round %d ended", round));
      if(result != null) {
        return result;
      }
    }

    throw new IllegalStateException("runRound in the last round did not return the winner");
  }

  /**
   * Sends the clients the 'end' message.
   *
   * @param winner the type of the client that won the game.
   * @throws ServerException if there is a problem with sending the message.
   */
  protected void gameOver(MessageUpdate winner) throws ServerException {
    if(winner == null)
      throw new IllegalArgumentException("winner must not be null");
    if(winner.getWinner() == null)
      throw new IllegalStateException("No winner was specified for the last message");
   
    switch(winner.getWinner()) {
    case DETECTIVE:
      detectiveWins++;
      break;
    case PHANTOM:
      phantomWins++;
    }
   
    Message detectiveMsg = null;
    Message phantomMsg = null;
    if(winner.getMoves().length == 0) {
      detectiveMsg = winner;
      phantomMsg = winner;
    }
    else {
      ClientType recipient = winner.getMoves()[0].getActor().getClientType();
      if(recipient != ClientType.DETECTIVE) {
        detectiveMsg = winner;
        phantomMsg = new MessageUpdate(graph, new ActorMove[0], winner.getWinner());
      } else {
        detectiveMsg = new MessageUpdate(graph, new ActorMove[0], winner.getWinner());
        phantomMsg = winner;
      }
    }
    detectiveClient.sendMessage(detectiveMsg);
    phantomClient.sendMessage(phantomMsg);
  }

  /**
   * Sends the clients the 'quit' message.
   *
   * @throws ServerException if there is a problem with sending the message.
   */
  protected void quit() throws ServerException {
    logger.info("Sending the 'quit' message to clients");
    Message msg = new MessageQuit();
    logger.debug("Sending the 'quit' message to detectives");
    detectiveClient.sendMessage(msg);
    logger.debug("Sending the 'quit' message to phantoms");
    phantomClient.sendMessage(msg);
    logger.debug("The message was sent");
  }
 
  /**
   * Runs the server.
   */
  public void run() throws ServerException {
    initClients();
   
    for(int game = 0; game < gameCount; game++) {
      logger.info(String.format("Starting game %d", game));
      MessageUpdate winner = runGame();
      gameOver(winner);
      logger.info(String.format("Game %d ended", game));
    }
   
    logger.info(String.format("Games won by the detectives: %d", detectiveWins));
    logger.info(String.format("Games won by the phantom: %d", phantomWins));

    quit();
  }
 
  /**
   * Writers information about placement of agents to the log.
   *
   * @param client the client that did the placement.
   * @param placement the message that contains the placement.
   */
  protected void logActorPlacement(Client client, MessageMove placement) {
    if(client == null)
      throw new IllegalArgumentException("client must not be null");
    if(placement == null)
      throw new IllegalArgumentException("placment must not be null");
   
    logger.debug(String.format("Placing actors (%s)", client.getClientName()));
    for(ActorMove move : placement.getMoves()) {
      logger.info(String.format("Actor: %s, node: %s", move.getActor().getId(), move.getTargetNode().getId()));
    }
  }
 
  /**
   * Writes information about movement of agents to the log.
   *
   * @param client the client that did the movement.
   * @param movement the message that contains the movement.
   */
  protected void logActorMovement(Client client, MessageMove movement) {
    if(client == null)
      throw new IllegalArgumentException("client must not be null");
    if(movement == null)
      throw new IllegalArgumentException("movement must not be null");
   
    logger.debug(String.format("Moving actors (%s)", client.getClientName()));
    for(ActorMove move : movement.getMoves()) {
      Actor actor = move.getActor();
      logger.info(String.format("Actor %s moved from node %s to node %s",
                      actor.getId(), actor.getCurrentPosition().getId(),
                      move.getTargetNode().getId()));
    }
  }
 
  /**
   * Initializes a new server.
   *
   * @param commandLineDetective command line for starting the detective client.
   * @param commandLinePhantom command line for starting the phantom client.
   */
  public Server(Graph sourceGraph, int gameCount, String[] commandLineDetective, String[] commandLinePhantom, String runName) {
    if(commandLineDetective == null || commandLineDetective.length == 0)
      throw new IllegalArgumentException("The command-line for the detectives was not supplied");
    if(commandLinePhantom == null || commandLinePhantom.length == 0)
      throw new IllegalArgumentException("The command-line for the phantom was not supplied");
    if(runName == null || runName.isEmpty())
      throw new IllegalArgumentException("runName must not be null");
    if(sourceGraph == null)
      throw new IllegalArgumentException("sourceGraph must not be null");
    if(gameCount <= 0)
      throw new IllegalArgumentException("gameCount must be a positive number");
   
    this.detectiveCommandLine = commandLineDetective;
    this.detectiveWins = 0;
    this.phantomCommandLine = commandLinePhantom;
    this.phantomWins = 0;
    this.runName = runName;
    this.sourceGraph = sourceGraph;
    this.gameCount = gameCount;
   
    loadConfiguration();
  }
 
  /**
   * Loads the configuration of the server.
   */
  protected void loadConfiguration() {
    Properties config = new Properties();
    InputStream configStream = null;
    try {
      configStream = ClassLoader.getSystemResourceAsStream(CONFIGURATION_NAME);
      config.load(configStream);
     
      String logMessagesStr = config.getProperty(CONFIGURATION_PROPERTY_LOG_MESSAGES);
      logMessages = Boolean.parseBoolean(logMessagesStr);
     
      String seedStr = config.getProperty(CONFIGURATION_PROPERTY_SEED);
      int seed = Integer.parseInt(seedStr);
      if(seed != 0)
        this.rnd = new Random(seed);
    }
    catch(Exception err) {
      logger.error("Could not read the configuration", err);
    }
    finally {
      try {
        if(configStream != null)
          configStream.close();
      }
      catch(IOException e) {
        logger.error("Could not close input stream", e);
      }
    }
  }
 
  /**
   * Prints usage information for the server to stderr.
   */
  public static void printUsage() {
    System.err.println("Usage: Server graph games name {phantom command line} \";\" {detective command line}");
    System.err.println("              graph is the name of the file with the graph");
    System.err.println("              games is the number of games");
    System.err.println("              what follows is the phantom and detective command lines separated");
    System.err.println("              by a single semicolon");
  }
 
  /**
   * Parses the command-line arguments and starts the server.
   *
   * @param args the command-line arguments.
   */
  public static void main(String[] args) {
    if(args.length < 5) {
      printUsage();
      return;
    }
   
    // Read the number of games played in the tournament
    int gameCount = 0;
    try {
      gameCount = Integer.parseInt(args[1]);
    }
    catch(NumberFormatException e) {
      System.err.println("The number of games is not a valid number");
      printUsage();
      return;
    }
   
    // Reads the name of the tournament (used in names of log files)
    String runName = args[2];
    if(runName.isEmpty()) {
      System.err.println("Run name must not be empty");
      printUsage();
      return;
    }
   
    // Extract command-lines for both clients
    int semicolonIndex = Arrays.asList(args).indexOf(";");
    if(semicolonIndex < 0) {
      System.err.println("The separator between the detective and phantom command lines was not found");
      printUsage();
      return;
    }
   
    String[] commandLinePhantom = Arrays.copyOfRange(args, 3, semicolonIndex);
    if(commandLinePhantom.length == 0) {
      System.err.println("The phantom command line was not specified");
      printUsage();
      return;
    }
    String[] commandLineDetective = Arrays.copyOfRange(args, semicolonIndex + 1, args.length);
    if(commandLineDetective.length == 0) {
      System.err.println("The detective command line was not specified");
      printUsage();
      return;
    }
   
    // Load the graph file
    File graphFile = null;
    try {
      graphFile = new File(args[0]);
    }
    catch(Exception err) {
      System.err.println("Invalid graph file name");
      printUsage();
      return;
    }
    Graph graph = null;
    try {
      graph = new Graph(graphFile);
    }
    catch(IOException e) {
      System.err.println("Could not read the graph file");
      e.printStackTrace();
    } catch (ParserException e) {
      System.err.println("The graph file has invalid format at line " + e.getLineNumber());
      e.printStackTrace();
    }
   
   
    Server server = new Server(graph, gameCount, commandLineDetective, commandLinePhantom, runName);
    try {
      server.run();
    }
    catch(ServerException e) {
      System.err.println("The server failed: " + e.getMessage());
      e.printStackTrace();
     
      server.killClientProcesses();
    }
  }
}
TOP

Related Classes of cz.matfyz.aai.fantom.server.Server

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.