package me.desht.chesscraft.chess;
import chesspresso.Chess;
import me.desht.chesscraft.ChessCraft;
import me.desht.chesscraft.Messages;
import me.desht.chesscraft.chess.player.HumanChessPlayer;
import me.desht.chesscraft.event.ChessGameCreatedEvent;
import me.desht.chesscraft.event.ChessGameDeletedEvent;
import me.desht.chesscraft.exceptions.ChessException;
import me.desht.chesscraft.util.ChessUtils;
import me.desht.dhutils.LogUtils;
import me.desht.dhutils.MiscUtil;
import me.desht.dhutils.UUIDFetcher;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.*;
public class ChessGameManager {
private static ChessGameManager instance = null;
private final Set<ChessGame> needToMigrate = new HashSet<ChessGame>();
private final Map<String,ChessGame> chessGames = new HashMap<String,ChessGame>();
private final Map<UUID,ChessGame> currentGame = new HashMap<UUID, ChessGame>();
private ChessGameManager() {
}
public static synchronized ChessGameManager getManager() {
if (instance == null) {
instance = new ChessGameManager();
}
return instance;
}
@SuppressWarnings("CloneDoesntCallSuperClone")
@Override
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public void registerGame(ChessGame game) {
String gameName = game.getName();
if (!chessGames.containsKey(gameName)) {
chessGames.put(gameName, game);
game.save();
Bukkit.getPluginManager().callEvent(new ChessGameCreatedEvent(game));
} else {
throw new ChessException("trying to register duplicate game " + gameName);
}
}
private void unregisterGame(String gameName) {
ChessGame game = getGame(gameName);
List<UUID> toRemove = new ArrayList<UUID>();
for (UUID playerId : currentGame.keySet()) {
if (currentGame.get(playerId) == game) {
toRemove.add(playerId);
}
}
for (UUID p : toRemove) {
currentGame.remove(p);
}
chessGames.remove(gameName);
Bukkit.getPluginManager().callEvent(new ChessGameDeletedEvent(game));
}
/**
* Delete the game of the given name. Permanent deletion is when a game is explicitly
* deleted; it is cleaned up and purged from disk. Temporary deletion occurs when the
* plugin is reloading games or being disabled and simply unregisters the game from the
* game manager.
*
* @param gameName Name of the game to delete
* @param permanent true if game is to be permanently deleted
*/
public void deleteGame(String gameName, boolean permanent) {
ChessGame game = getGame(gameName);
if (permanent) {
ChessCraft.getInstance().getPersistenceHandler().unpersist(game);
}
game.onDeleted(permanent);
unregisterGame(gameName);
}
public boolean checkGame(String gameName) {
return chessGames.containsKey(gameName);
}
public Collection<ChessGame> listGamesSorted() {
SortedSet<String> sorted = new TreeSet<String>(chessGames.keySet());
List<ChessGame> res = new ArrayList<ChessGame>();
for (String name : sorted) {
res.add(chessGames.get(name));
}
return res;
}
public Collection<ChessGame> listGames() {
return chessGames.values();
}
public ChessGame getGame(String name) {
return getGame(name, true);
}
public ChessGame getGame(String name, boolean fuzzy) {
if (!chessGames.containsKey(name)) {
if (fuzzy && chessGames.size() > 0) {
// try "fuzzy" search
Set<String> strings = chessGames.keySet();
String keys[] = strings.toArray(new String[strings.size()]);
String matches[] = ChessUtils.fuzzyMatch(name, keys, 3);
if (matches.length == 1) {
return chessGames.get(matches[0]);
} else {
// partial-name search
int k = -1, c = 0;
name = name.toLowerCase();
for (int i = 0; i < keys.length; ++i) {
if (keys[i].toLowerCase().startsWith(name)) {
k = i;
++c;
}
}
if (k >= 0 && c == 1) {
return chessGames.get(keys[k]);
}
}
// TODO: if multiple matches, check if only one is waiting for
// more players (and return that one)
}
throw new ChessException(Messages.getString("Game.noSuchGame", name)); //$NON-NLS-1$
}
return chessGames.get(name);
}
public void setCurrentGame(UUID uuid, String gameName) {
currentGame.put(uuid, getGame(gameName));
}
public void setCurrentGame(Player player, ChessGame game) {
currentGame.put(player.getUniqueId(), game);
}
public ChessGame getCurrentGame(Player player) {
return getCurrentGame(player, false);
}
public ChessGame getCurrentGame(Player player, boolean verify) {
ChessGame game = currentGame.get(player.getUniqueId());
if (verify && game == null) {
throw new ChessException(Messages.getString("Game.noActiveGame"));
}
return game;
}
public Map<UUID, String> getCurrentGames() {
Map<UUID, String> res = new HashMap<UUID, String>();
for (UUID s : currentGame.keySet()) {
ChessGame game = currentGame.get(s);
if (game != null) {
res.put(s, game.getName());
}
}
return res;
}
/**
* Create a unique game name based on the player's name.
*
* @param playerName player's name
* @return a unique game name
*/
private String makeUniqueGameName(String playerName) {
String res;
int n = 1;
do {
res = playerName + "-" + n++; //$NON-NLS-1$
} while (checkGame(res));
return res;
}
/**
* Convenience method to create a new chess game.
*
* @param player the player who is creating the game
* @param gameName name of the game - may be null, in which case a name
* will be generated
* @param boardName name of the board for the game - may be null, in which
* case an arbitrary free board will be picked
* @return the new game
* @throws ChessException if there is any problem creating the game
*/
public ChessGame createGame(Player player, String gameName, String boardName, int colour) {
BoardView bv;
if (boardName == null) {
bv = BoardViewManager.getManager().getFreeBoard();
} else {
bv = BoardViewManager.getManager().getBoardView(boardName);
}
return createGame(player, gameName, bv, colour);
}
/**
* Convenience method to create a new chess game.
*
* @param player the player who is creating the game
* @param gameName name of the game - may be null, in which case a name
* will be generated
* @param bv the board for the game - may not be null
* @return the new game
* @throws ChessException if there is any problem creating the game
*/
public ChessGame createGame(Player player, String gameName, BoardView bv, int colour) {
if (gameName == null || gameName.equals("-")) {
gameName = makeUniqueGameName(player.getName());
}
String tcSpec = bv.getControlPanel().getTcDefs().currentDef().getSpec();
ChessGame game = new ChessGame(gameName, player, tcSpec, colour);
registerGame(game);
bv.setGame(game);
setCurrentGame(player, game);
MiscUtil.statusMessage(player, Messages.getString("ChessCommandExecutor.gameCreated", game.getName(), bv.getName()));
return game;
}
/**
* A game with an old-style player name has been loaded; add it to the list of games which will
* be asychronously migrated to player UUIDs. (Must be async since we'll be contacting Mojang's
* API service, which could block)
*
* @param game the game to be migrated
*/
public void needToDoUUIDMigration(ChessGame game) {
needToMigrate.add(game);
}
/**
* Carry out the migration of old-style player names to UUIDs. This is done asynchronously.
*/
public void checkForUUIDMigration() {
final List<String> names = new ArrayList<String>();
final List<GameAndColour> gameAndColours = new ArrayList<GameAndColour>();
for (ChessGame game : needToMigrate) {
if (game.hasPlayer(Chess.WHITE) && game.getPlayer(Chess.WHITE).isHuman()) {
HumanChessPlayer hcp = (HumanChessPlayer) game.getPlayer(Chess.WHITE);
if (hcp.getOldStyleName() != null) {
names.add(hcp.getOldStyleName());
gameAndColours.add(new GameAndColour(game, hcp.getColour()));
}
}
if (game.hasPlayer(Chess.BLACK) && game.getPlayer(Chess.BLACK).isHuman()) {
HumanChessPlayer hcp = (HumanChessPlayer) game.getPlayer(Chess.BLACK);
if (hcp.getOldStyleName() != null) {
names.add(hcp.getOldStyleName());
gameAndColours.add(new GameAndColour(game, hcp.getColour()));
}
}
}
needToMigrate.clear();
if (names.size() > 0) {
LogUtils.info("migrating " + names.size() + " player names to UUID in saved game files");
Bukkit.getScheduler().runTaskAsynchronously(ChessCraft.getInstance(), new Runnable() {
@Override
public void run() {
UUIDFetcher uf = new UUIDFetcher(names, true);
try {
Bukkit.getScheduler().runTask(ChessCraft.getInstance(), new SyncTask(uf.call(), gameAndColours));
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
private class SyncTask implements Runnable {
private final Map<String, UUID> map;
private final List<GameAndColour> gameAndColours;
public SyncTask(Map<String, UUID> map, List<GameAndColour> gameAndColours) {
this.map = map;
this.gameAndColours = gameAndColours;
}
@Override
public void run() {
for (GameAndColour gc : gameAndColours) {
HumanChessPlayer hcp = (HumanChessPlayer) gc.game.getPlayer(gc.colour);
gc.game.migratePlayer(gc.colour, hcp.getOldStyleName(), map.get(hcp.getOldStyleName()));
}
for (ChessGame game : listGames()) {
game.save();
}
LogUtils.info("player name -> UUID migration complete");
}
}
private class GameAndColour {
private final ChessGame game;
private final int colour;
private GameAndColour(ChessGame game, int colour) {
this.game = game;
this.colour = colour;
}
}
}