Package tud.gamecontroller.players

Source Code of tud.gamecontroller.players.RemotePlayer

/*
    Copyright (C) 2008-2010 Stephan Schiffel <stephan.schiffel@gmx.de>
                  2010 Nicolas JEAN <njean42@gmail.com>

    This file is part of GameController.

    GameController 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.

    GameController 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 GameController.  If not, see <http://www.gnu.org/licenses/>.
*/

package tud.gamecontroller.players;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Collection;

import tud.gamecontroller.ConnectionEstablishedNotifier;
import tud.gamecontroller.GDLVersion;
import tud.gamecontroller.auxiliary.InvalidKIFException;
import tud.gamecontroller.game.JointMoveInterface;
import tud.gamecontroller.game.MoveInterface;
import tud.gamecontroller.game.RoleInterface;
import tud.gamecontroller.game.RunnableMatchInterface;
import tud.gamecontroller.game.StateInterface;
import tud.gamecontroller.game.impl.Game;
import tud.gamecontroller.game.impl.Move;
import tud.gamecontroller.logging.GameControllerErrorMessage;
import tud.gamecontroller.scrambling.GameScramblerInterface;
import tud.gamecontroller.term.TermInterface;

public class RemotePlayer<TermType extends TermInterface,
  StateType extends StateInterface<TermType, ? extends StateType>> extends AbstractPlayer<TermType, StateType>  {
 
  private String host;
  private InetAddress hostAddress;
  private int port;
  // private MoveFactoryInterface<? extends MoveInterface<TermType>> movefactory;
  private GameScramblerInterface gameScrambler;
  protected boolean firstTurn;

 
  /**
   * the idea to handle connection timeouts by Sam Schreiber <schreib@cs.stanford.edu>:
   * "[...] one way to deal with unresponsive
   * players might be to give each player a total of (for example) 10 extra
   * seconds total over the course of the match to spend establishing the
   * connection, and then only cut them off with a connection timeout if they
   * spend all of that time [...]"
   */
  private int connectionTimeoutBonus;

  /**
   * the maximum time (milliseconds) to wait until the connection to the player is established
   * (the start/playclock only start after that)
   */
  private static final int CONNECTION_TIMEOUT = 2000;
  private static final int CONNECTION_TIMEOUT_BONUS = 30000;
 
  public RemotePlayer(String name, String host, int port, GDLVersion gdlVersion, GameScramblerInterface gamescrambler) {
    super(name, gdlVersion);
    this.host=host;
    this.port=port;
    this.gameScrambler=gamescrambler;
  }
 
  @Override
  public void gameStart(RunnableMatchInterface<TermType, StateType> match, RoleInterface<TermType> role, ConnectionEstablishedNotifier notifier) {
   
    super.gameStart(match, role, notifier);
    this.firstTurn = true;
    String gameDescription = match.getGame().getKIFGameDescription();
    // if this is a Regular GDL game and the player understands GDL-II, add necessary sees(Role,Did(Role2,Move)) ← true(does(Role2,Move))
    if (match.getGame().getGdlVersion() == GDLVersion.v1 && this.getGdlVersion() == GDLVersion.v2) {
      gameDescription += Game.DEFAULT_SEES_RULES;
    }
   
    hostAddress = null; // don't use an old hostAddress for a new match
    connectionTimeoutBonus = CONNECTION_TIMEOUT_BONUS;
    String msg="(START "+
        match.getMatchID()+" "+
        gameScrambler.scramble(role.getKIFForm()).toUpperCase()+
        " ("+gameScrambler.scramble(gameDescription).toUpperCase()+") "+
        match.getStartclock()+" "+match.getPlayclock()+")";
    notifyStartRunning();
    String reply=sendMsg(msg, notifier);
    notifyStopRunning();
    logger.info("reply from "+this.getName()+": "+reply+ " after "+getLastMessageRuntime()+"ms");
   
     
  }

  public MoveInterface<TermType> gamePlay(Object seesTerms, ConnectionEstablishedNotifier notifier) {
    MoveInterface<TermType> move=null;
    String msg = constructPlayOrStopMessage("PLAY", seesTerms);
    String reply, descrambledReply;
    notifyStartRunning();
    reply=sendMsg(msg, notifier);
    notifyStopRunning();
    logger.info("reply from "+this.getName()+": "+reply+ " after "+getLastMessageRuntime()+"ms");
    if(reply!=null){
      descrambledReply=gameScrambler.descramble(reply);
      try {
        TermType moveTerm = match.getGame().getTermFromString(descrambledReply);
        if(moveTerm!=null && !moveTerm.isGround())
          throw new InvalidKIFException("\""+descrambledReply+"\" is not a ground term.");
        move = new Move<TermType>(moveTerm);
      } catch (InvalidKIFException ex) {
        String message = "Error parsing reply \""+reply+"\" from "+this+": "+ex.getMessage();
        logErrorMessage(GameControllerErrorMessage.PARSING_ERROR, message);
      }
//      if(moveterm!=null && !moveterm.isGround()){
//        logger.severe("Reply \""+reply+"\" from "+this+" is not a ground term. (descrambled:\""+descrambledReply+"\")");
//      }else if(moveterm!=null){
//        move=new MoveType(moveterm);
//      }
    }
    return move;
  }

  @Override
  public void gameStop(Object seesTerms, ConnectionEstablishedNotifier notifier) {
    String msg = constructPlayOrStopMessage("STOP", seesTerms);
    //notifyStartRunning(); // don't count time for the stop message
    /*String reply=*/ sendMsg(msg, notifier);
    //notifyStopRunning();
    //logger.info("reply from "+this.getName()+": "+reply+ " after "+getLastMessageRuntime()+"ms");
  }

  /**
   * This is the core of GDL-II:
   * Instead of sending the actual joint move to the remote players, they only get the computed "sees terms".
   * (and this is the only place where we apply two different behaviors: send the sees terms in GDL-II games,
   * and send the prior moves in regular GDL games)
   */
  @SuppressWarnings("unchecked")
  private String constructPlayOrStopMessage(String messageType, Object seesTerms) {
    StringBuilder msg = new StringBuilder("(");
    msg.append(messageType).append(" ").append(match.getMatchID()).append(" ");
    if (this.firstTurn) {
      this.firstTurn = false;
      msg.append("NIL");
    }else{
      if (getGdlVersion() == GDLVersion.v1) { // GDL-I player
        JointMoveInterface<TermType> jointMove = (JointMoveInterface<TermType>) seesTerms;
        msg.append(gameScrambler.scramble(jointMove.getKIFForm()).toUpperCase());
      } else { // GDL-II player
        // Let's send the seesTerms (or jointMove transformed into seesTerms) to each player via the PLAY message
        msg.append("(");
        for (TermType t:(Collection<TermType>) seesTerms) {
          msg.append(gameScrambler.scramble(t.getKIFForm()).toUpperCase()).append(" ");
        }
        msg.append(")");
      }
    }
    msg.append(")");
    return msg.toString();
  }

  private String sendMsg(String msg, ConnectionEstablishedNotifier notifier) {
    String reply=null;
    Socket s=null;
    OutputStream out=null;
    InputStream is=null;
    InetAddress hostAddress=null;
   
    try {
      logger.info("Begin creating Socket for " + this);
      long t0 = System.currentTimeMillis();
      hostAddress = getHostAddress();
      int dnsTime = (int)(System.currentTimeMillis() - t0);
      s = new Socket();
      try {
        s.connect(new InetSocketAddress(hostAddress, port), CONNECTION_TIMEOUT + connectionTimeoutBonus - dnsTime);
      } catch(InterruptedIOException e) {
        connectionTimeoutBonus -= Math.max(0, System.currentTimeMillis() - t0 - CONNECTION_TIMEOUT); // subtract the excess connection time from the bonus if greater than CONNECTION_TIMEOUT
        throw e; // will be catched again below
      }
      connectionTimeoutBonus -= Math.max(0, System.currentTimeMillis() - t0 - CONNECTION_TIMEOUT); // subtract the excess connection time from the bonus if greater than CONNECTION_TIMEOUT
      notifier.connectionEstablished();
      logger.info("Done creating Socket for " + this);
     
      out=s.getOutputStream();
      PrintWriter pw=new PrintWriter(out);
      pw.print("POST / HTTP/1.0\r\n");
      pw.print("Accept: text/delim\r\n");
      pw.print("Sender: Gamecontroller\r\n");
      pw.print("Receiver: "+host+"\r\n");
      pw.print("Content-type: text/acl\r\n")
      pw.print("Content-length: "+msg.length()+"\r\n")
      pw.print("\r\n");
 
      pw.print(msg);
      pw.flush();
      logger.info("message to "+this.getName()+" sent: \"" + msg+ "\"");
     
      is = s.getInputStream();
      if ( is == null) return null;
      BufferedReader in = new BufferedReader( new InputStreamReader( is ));
      String line;
      line = in.readLine();
      while( line!=null && line.trim().length() > 0 ){
        line = in.readLine();
      }
 
      char[] cbuf=new char[1024];
      int len;
      while((len=in.read(cbuf,0,1023))!=-1){
        line=new String(cbuf,0,len);
        if(reply==null) reply=line;
        else reply += line;
      }
    } catch (InterruptedIOException e) {
      String message = "error: io error for "+ this+" : "+e.getMessage();
      logErrorMessage(GameControllerErrorMessage.IO_ERROR, message);
      Thread.currentThread().interrupt();
    } catch (UnknownHostException e) {
      String message = "error: unknown host \""+ host+ "\"";
      logErrorMessage(GameControllerErrorMessage.UNKNOWN_HOST, message);
    } catch (IOException e) {
      String message = "error: io error for "+ this+" : "+e.getMessage();
      logErrorMessage(GameControllerErrorMessage.IO_ERROR, message);
    } finally {
      try{
        if(out!=null) out.close();
        if(is!=null) is.close();
        if(s!=null) s.close();
      }catch(Exception ex){ };
      // call the notifier in case of an exception, otherwise
      // the GameController will wait forever if the exception occurred
      // before the sending of the message
      notifier.connectionEstablished();
    }
    return reply;
  }

  @Override
  public String toString(){
    StringBuilder sb = new StringBuilder("remote(");
    sb.append(getName());
    String ip = (hostAddress!=null?hostAddress.getHostAddress():null);
    if(!host.equals(ip)) {
      sb.append(", host:").append(host);
    }
    if(ip!=null) {
      sb.append(", ip:").append(ip);
    }
    sb.append(", port:").append(port).append(")");
    return sb.toString();
  }

  private InetAddress getHostAddress() throws UnknownHostException {
    if (hostAddress == null) {
      hostAddress = InetAddress.getByName(host);
    }
    return hostAddress;
  }
}
TOP

Related Classes of tud.gamecontroller.players.RemotePlayer

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.