///////////////////////////////////////////////////////////////////////
// STANFORD LOGIC GROUP //
// General Game Playing Project //
// //
// Sample Player Implementation //
// //
// (c) 2007. See LICENSE and CONTRIBUTORS. //
///////////////////////////////////////////////////////////////////////
package stanfordlogic.game;
import java.io.PrintStream;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;
import stanfordlogic.game.Gamer;
import stanfordlogic.gdl.GdlAtom;
import stanfordlogic.gdl.GdlExpression;
import stanfordlogic.gdl.GdlList;
import stanfordlogic.gdl.Parser;
import stanfordlogic.gdl.SymbolTable;
import stanfordlogic.network.RequestHandler;
import stanfordlogic.util.Triple;
/**
* The Game Manager encapsulates the global program state.
*/
public class GameManager
{
/** The global parser. Global to avoid several allocations; safe because
* parser is reentrable. */
private static Parser parser_ = new Parser();
/** The global parser's symbol table. */
private static SymbolTable symbolTable_ = parser_.getSymbolTable();
/** The factory used to create games from descriptions. */
private static GamerFactory gamerFactory_ = null;
/** Maps a game ID to a gamer for currently active games. */
private static Map<String, Gamer> games_ = new TreeMap<String, Gamer>();
private static final Logger logger_ = Logger.getLogger("stanfordlogic.game");
// These get automatically initialized to zero.
final private static long [] statisticsTime_ = new long[5];
final private static int [] statisticsNumber_ = new int[5];
public static final int TIME_METAGDL = 0;
public static final int TIME_GET_AN_ANSWER = 1;
public static final int TIME_GET_ALL_ANSWERS = 2;
/**
* Note that we spent <tt>howMuch</tt> nanoseconds in category
* <tt>category</tt>.
*
* @param category
* Category time was spent in.
* @param howMuch
* Time spent in nanoseconds.
*/
public static void addTime(int category, long howMuch)
{
statisticsTime_[category] += howMuch;
statisticsNumber_[category]++;
}
public static double getAverageTime(int category)
{
if ( statisticsNumber_[category] == 0 )
return 0;
else
return ((double) statisticsTime_[category]) / statisticsNumber_[category];
}
public static long getTotalTime(int category)
{
return statisticsTime_[category];
}
public static int getNumTime(int category)
{
return statisticsNumber_[category];
}
public static String getCategoryName(int category)
{
switch (category)
{
case TIME_METAGDL:
return "MetaGDL";
case TIME_GET_AN_ANSWER:
return "GetAnAnswer";
case TIME_GET_ALL_ANSWERS:
return "GetAllAnswers";
default:
return "Unknown!!";
}
}
public static void printTimeStats(PrintStream output)
{
for ( int i = 0; i <= TIME_GET_ALL_ANSWERS; i++ )
{
output.print(getCategoryName(i));
output.print(": ");
output.print( ((double) getTotalTime(i)) / 1000000 );
output.print(" ms (n = ");
output.print(getNumTime(i));
output.print("; average = ");
output.print( getAverageTime(i) / 1000000 );
output.println(" ms)");
}
}
/**
* Get the global parser's symbol table.
* @return The global symbol table.
*/
public static SymbolTable getSymbolTable()
{
return symbolTable_;
}
/**
* Get the global parser.
* @return The global parser.
*/
public static Parser getParser()
{
return parser_;
}
/**
* Get the gamer factory in use to create gamers.
* @return The GamerFactory currently in use.
*/
public static GamerFactory getGamerFactory()
{
return gamerFactory_;
}
/**
* @param gamerFactory The GamerFactory to use for creating new games.
*/
public static void setGamerFactory(GamerFactory gamerFactory)
{
gamerFactory_ = gamerFactory;
}
/**
* Called by the connection manager upon each new incoming connection.
*
* @param handler The handler that needs to be processed.
*/
public static void newRequest(RequestHandler handler)
{
try
{
// Launch the handler thread
handler.start();
}
catch(Exception e)
{
logger_.severe("Network failure: " + e.getMessage());
}
}
/**
* Starts a new game. Takes the information from the start message
* and gives it to the gamer factory, which creates an appropriate
* gamer for the game.
*
* @param gameId The identifier of the game being started.
* @param role The role I am playing in the new game.
* @param description The description (rules) of the game.
* @param startClock The time given to think about the game.
* @param playClock The time given to make a move.
*
* @return The Gamer instance created to play the game.
*
* @see stanfordlogic.Gamer
*/
public static Gamer newGame(String gameId, GdlAtom role, GdlList description,
int startClock, int playClock)
{
synchronized(GameManager.class)
{
if (gamerFactory_ == null) {
logger_.severe("No gamer factory set!");
return null;
}
// Make sure this game isn't already active:
if (games_.containsKey(gameId)) {
logger_.severe("Game already active: " + gameId);
return null;
}
logger_.info("");
logger_.info("-----------------------------------------------");
logger_.info("NEW GAME!");
logger_.info("");
logger_.info(" My role : " + role.toString() );
logger_.info("Start clock : " + startClock);
logger_.info(" Play clock : " + playClock);
logger_.info("");
// put in a temporary game:
games_.put(gameId, null);
}
Gamer g = gamerFactory_.makeGamer(gameId, role, description, startClock, playClock);
synchronized(GameManager.class)
{
games_.put(gameId, g);
}
return g;
}
/**
* Ends the game specified by <tt>gameId</tt>. The final moves are
* processed and the payoffs are computed and printed to the logger.
*
* @param gameId Name of the game to terminate.
* @param prevMoves The last set of moves made in the game.
*/
public static synchronized void endGame(String gameId, GdlList prevMoves)
{
Gamer gamer = games_.get(gameId);
if (gamer == null)
{
logger_.severe(gameId + ": WARNING: Attempting to terminate game [" + gameId
+ "], but no such game");
return;
}
try
{
StringBuilder prevMovesStr = new StringBuilder();
prevMovesStr.append(" Previous moves: ");
for ( GdlExpression exp : prevMoves )
{
prevMovesStr.append(exp.toString());
prevMovesStr.append(" ");
}
logger_.info(gameId + ": Beginning payoff computation." + prevMovesStr);
// Get the list of payoffs: <Role, Payoff, IsMe>
List<Triple<String, Integer, Boolean>> results = gamer.getPayoffs(prevMoves);
// Figure out what the longest role is
int maxRoleLength = 0;
for ( Triple<String, Integer, Boolean> res : results )
maxRoleLength = Math.max(maxRoleLength, res.first.length());
// Print out the payoffs
for ( Triple<String, Integer, Boolean> res : results )
{
// print the right amount of spaces (so that things line up right)
StringBuilder spacing = new StringBuilder();
for ( int i = 0; i < maxRoleLength - res.first.length(); i++ )
spacing.append(" ");
logger_.info(" " + ( res.third ? "->" : " " ) + " " + res.first
+ spacing + " " + res.second + " "
+ ( res.third ? "<-" : " " ));
}
}
catch (Exception e)
{
logger_.severe(gameId + ": Error computing payoff: " + e.getClass().getName() + " - " + e.getMessage());
}
// tell the game it's time to die.
gamer.stopIt();
games_.remove(gameId);
}
/**
* Get the Gamer associated with <tt>gameId</tt>.
*
* @param gameId The name of the gamer to get.
* @return The Gamer object associated with <tt>gameId</tt>.
*
* @see Gamer
*/
public static synchronized Gamer getGame(String gameId)
{
return games_.get(gameId);
}
}