package me.desht.chesscraft.chess.ai; import me.desht.chesscraft.ChessCraft; import me.desht.chesscraft.DirectoryStructure; import me.desht.chesscraft.Messages; import me.desht.chesscraft.chess.ChessGame; import me.desht.chesscraft.exceptions.ChessException; import me.desht.dhutils.Debugger; import me.desht.dhutils.JARUtil; import me.desht.dhutils.LogUtils; import me.desht.dhutils.MiscUtil; import org.bukkit.ChatColor; import org.bukkit.configuration.Configuration; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.MemoryConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import java.io.File; import java.io.InputStream; import java.lang.reflect.Constructor; import java.util.*; import java.util.Map.Entry; /** * @author desht * * This class is responsible for creating and managing the AI definitions and instances. * */ public class AIFactory { private static final String AI_ALIASES_FILE = "AI.yml"; private static final String AI_CORE_DEFS = "/AI_settings.yml"; private final HashMap<String, ChessAI> runningAIs = new HashMap<String, ChessAI>(); private final Map<String, AIDefinition> allAliases = new HashMap<String, AIDefinition>(); private final Map<String, AIDefinition> coreDefs = new HashMap<String, AIDefinition>(); private static AIFactory instance; public AIFactory() { loadAIDefinitions(); } public static synchronized AIFactory getInstance() { if (instance == null) { instance = new AIFactory(); } return instance; } @SuppressWarnings("CloneDoesntCallSuperClone") @Override public Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } public ChessAI getNewAI(ChessGame game, String aiName, boolean isWhiteAI) { return getNewAI(game, aiName, false, isWhiteAI); } public ChessAI getNewAI(ChessGame game, String aiName, boolean forceNew, boolean isWhiteAI) { if (!forceNew) { int max = ChessCraft.getInstance().getConfig().getInt("ai.max_ai_games"); //$NON-NLS-1$ if (max == 0) { throw new ChessException(Messages.getString("ChessAI.AIdisabled")); //$NON-NLS-1$ } else if (runningAIs.size() >= max) { throw new ChessException(Messages.getString("ChessAI.noAvailableAIs", max)); //$NON-NLS-1$ } } AIDefinition aiDef = getAIDefinition(aiName); if (aiDef == null) { throw new ChessException(Messages.getString("ChessAI.AInotFound")); //$NON-NLS-1$ } else if (runningAIs.containsKey(aiDef.getName())) { throw new ChessException(Messages.getString("ChessAI.AIbusy")); //$NON-NLS-1$ } ChessAI ai = aiDef.createInstance(game, isWhiteAI); runningAIs.put(aiName, ai); return ai; } void deleteAI(ChessAI ai) { runningAIs.remove(ai.getName()); } /** * Check if the given AI name is available (i.e. not in a game). * * @param aiName name of the AI to check * @return true if the AI is available */ public boolean isAvailable(String aiName) { return !runningAIs.containsKey(aiName); } /** * Clear down all running AIs. Called on disable. */ public void clearDown() { List<ChessAI> l = new ArrayList<ChessAI>(); for (Entry<String, ChessAI> e : runningAIs.entrySet()) { l.add(e.getValue()); } for (ChessAI ai : l) { ai.delete(); } } public List<AIDefinition> listAIDefinitions() { return listAIDefinitions(true); } public List<AIDefinition> listAIDefinitions(boolean isSorted) { if (isSorted) { SortedSet<String> sorted = new TreeSet<String>(allAliases.keySet()); List<AIDefinition> res = new ArrayList<AIDefinition>(); for (String name : sorted) { res.add(allAliases.get(name)); } return res; } else { return new ArrayList<AIDefinition>(allAliases.values()); } } /** * Return the AI definition for the given AI name. * * @param aiName the name of the AI * @return the AI definition */ public AIDefinition getAIDefinition(String aiName) { if (aiName.startsWith(ChessAI.AI_PREFIX)) { aiName = aiName.substring(ChessAI.AI_PREFIX.length()); } if (allAliases.containsKey(aiName)) { return allAliases.get(aiName); } else { return coreDefs.get(aiName); } } public AIDefinition getAIDefinition(String aiName, boolean force) { AIDefinition def = getAIDefinition(aiName); if (def == null && force) { throw new ChessException(Messages.getString("ChessAI.AInotFound")); } return def; } /** * Get the name of a random free and enabled AI. * * @return a random AI name which is currently available * @throws ChessException if there are no free AIs */ public String getFreeAIName() { List<String> free = new ArrayList<String>(); for (String k : allAliases.keySet()) { if (isAvailable(k) && allAliases.get(k).isEnabled()) { free.add(k); } } if (free.size() == 0) throw new ChessException(Messages.getString("ChessAI.noAvailableAIs", allAliases.size())); return ChessAI.AI_PREFIX + free.get(new Random().nextInt(free.size())); } public void loadAIDefinitions() { YamlConfiguration coreAIdefs; allAliases.clear(); // first pull in the core definitions from the JAR file resource... try { JARUtil ju = new JARUtil(ChessCraft.getInstance()); InputStream in = ju.openResourceNoCache(AI_CORE_DEFS); coreAIdefs = YamlConfiguration.loadConfiguration(in); } catch (Exception e) { LogUtils.severe("Can't load AI definitions: " + e.getMessage()); return; } // now load the aliases file File aiAliasesFile = new File(DirectoryStructure.getPluginDirectory(), AI_ALIASES_FILE); Configuration aliasesConf = YamlConfiguration.loadConfiguration(aiAliasesFile); for (String alias : aliasesConf.getKeys(false)) { ConfigurationSection aliasConf = aliasesConf.getConfigurationSection(alias); String ai = aliasConf.getString("ai"); if (!coreAIdefs.contains(ai)) { LogUtils.warning("AI aliases file " + aiAliasesFile + " refers to non-existent AI definition: " + alias); continue; } ConfigurationSection core = coreAIdefs.getConfigurationSection(ai); for (String key : core.getKeys(false)) { if (!aliasConf.contains(key)) { aliasConf.set(key, core.get(key)); } } try { AIDefinition aiDef = new AIDefinition(alias, aliasConf); allAliases.put(alias, aiDef); coreDefs.put(ai, aiDef); } catch (ClassNotFoundException e) { LogUtils.warning("unknown class '" + aliasConf.getString("class") + "' for AI [" + alias + "]: skipped"); } catch (ClassCastException e) { LogUtils.warning("class '" + aliasConf.getString("class") + "'for AI [" + alias + "] is not a AbstractAI subclass: skipped"); } } Debugger.getInstance().debug("Loaded " + allAliases.size() + " AI definitions"); } public class AIDefinition { private final ConfigurationSection params; private final Class<? extends ChessAI> aiImplClass; private final String name; public AIDefinition(String name, ConfigurationSection conf) throws ClassNotFoundException { this.name = name; this.params = new MemoryConfiguration(); String className = conf.getString("class", "me.desht.chesscraft.chess.ai.JChecsAI"); if (className.indexOf('.') == -1) className = "me.desht.chesscraft.chess.ai." + className; aiImplClass = Class.forName(className).asSubclass(ChessAI.class); for (String k : conf.getKeys(false)) { params.set(k, conf.get(k)); } Debugger.getInstance().debug(2, "loaded " + aiImplClass.getName() + " for AI " + name); } public ChessAI createInstance(ChessGame game, boolean isWhiteAI) { try { Constructor<? extends ChessAI> ctor = aiImplClass.getDeclaredConstructor(String.class, ChessGame.class, Boolean.class, ConfigurationSection.class); return ctor.newInstance(name, game, isWhiteAI, params); } catch (Exception e) { LogUtils.warning("Caught " + e.getClass().getName() + " while loading AI " + name); LogUtils.warning(" Exception message: " + e.getMessage()); e.printStackTrace(); throw new ChessException("internal error while creating AI " + name); } } public String getImplClassName() { return aiImplClass.getSimpleName(); } public String getName() { return name; } public String getDisplayName() { return ChessAI.AI_PREFIX + name; } public List<String> getDetails() { List<String> res = new ArrayList<String>(); res.add("AI " + getDisplayName() + " (" + getImplClassName() + ") :"); for (String k : MiscUtil.asSortedList(params.getKeys(false))) { res.add(ChatColor.DARK_RED + "* " + ChatColor.WHITE + k + ": " + ChatColor.YELLOW + params.get(k)); } return res; } public String getEngine() { return getParams().getString("engine"); } public double getPayoutMultiplier() { return getParams().getDouble("payout_multiplier", 1.0); } public String getComment() { return getParams().getString("comment"); } public boolean isEnabled() { return getParams().getBoolean("enabled", true); } public ConfigurationSection getParams() { return params; } } }