package me.desht.chesscraft.chess; import com.google.common.base.Joiner; import me.desht.chesscraft.exceptions.ChessException; import me.desht.chesscraft.util.ChessUtils; import me.desht.dhutils.Debugger; import org.bukkit.configuration.serialization.ConfigurationSerializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class TimeControl implements ConfigurationSerializable { public enum ControlType { NONE, ROLLOVER, MOVE_IN, GAME_IN } private final String spec; private final ControlType controlType; private final long totalTime; // milliseconds private long remainingTime; // milliseconds private long elapsed; // milliseconds private int rolloverPhase; private int rolloverMovesMade; private final List<RolloverPhase> rollovers = new ArrayList<TimeControl.RolloverPhase>(); private long lastChecked = System.currentTimeMillis(); private boolean active = false; private boolean newPhase; public TimeControl() { this(0L); } public TimeControl(String specStr) { spec = specStr.toUpperCase(); if (spec.isEmpty() || spec.startsWith("N")) { totalTime = 0L; controlType = ControlType.NONE; } else if (spec.startsWith("G/")) { // game in - minutes int t = Integer.parseInt(spec.substring(2)); remainingTime = totalTime = t * 60000; controlType = ControlType.GAME_IN; } else if (spec.startsWith("M/")) { // move in - seconds int t = Integer.parseInt(spec.substring(2)); remainingTime = totalTime = t * 1000; controlType = ControlType.MOVE_IN; } else if (!spec.isEmpty() && Character.isDigit(spec.charAt(0))) { totalTime = 0L; for (String s0 : spec.split(";")) { rollovers.add(new RolloverPhase(s0)); } rolloverPhase = rolloverMovesMade = 0; remainingTime = rollovers.get(0).getMinutes() * 60000; controlType = ControlType.ROLLOVER; } else { throw new ChessException("Invalid time control specification: " + spec); } } public TimeControl(long elapsed) { controlType = ControlType.NONE; this.elapsed = elapsed; this.spec = ""; this.remainingTime = this.totalTime = 0L; this.rolloverMovesMade = this.rolloverPhase = 0; } @Override public Map<String, Object> serialize() { Map<String, Object> res = new HashMap<String, Object>(); res.put("spec", spec); res.put("elapsed", elapsed); res.put("remainingTime", remainingTime); res.put("rolloverPhase", rolloverPhase); res.put("rolloverMovesMade", rolloverMovesMade); return res; } public static TimeControl deserialize(Map<String, Object> map) { TimeControl tc = new TimeControl((String) map.get("spec")); tc.elapsed = Long.parseLong(map.get("elapsed").toString()); tc.remainingTime = Long.parseLong(map.get("remainingTime").toString()); tc.rolloverMovesMade = (Integer) map.get("rolloverMovesMade"); tc.rolloverPhase = (Integer) map.get("rolloverPhase"); return tc; } public ControlType getControlType() { return controlType; } public long getTotalTime() { return totalTime; } public long getElapsed() { return elapsed; } public String getSpec() { return spec; } public boolean isNewPhase() { return newPhase; } public boolean isActive() { return active; } public void setActive(boolean active) { lastChecked = System.currentTimeMillis(); // ensure the next tick() gets a good offset this.active = active; } public long getRemainingTime() { return controlType == ControlType.NONE ? Long.MAX_VALUE : remainingTime; } public String getClockString() { switch (getControlType()) { case NONE: return ChessUtils.milliSecondsToHMS(getElapsed()); default: return ChessUtils.milliSecondsToHMS(getRemainingTime()); } } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { // TODO: i18n needed here switch (controlType) { case MOVE_IN: return "Move in " + (totalTime / 1000) + "s"; case GAME_IN: return "Game in " + (totalTime / 60000) + "m"; case ROLLOVER: List<String> l = new ArrayList<String>(); for (int i = 0; i < rollovers.size(); i++) { if (i == rolloverPhase) { l.add("[ " + rollovers.get(i).toString() + " ]"); } else { l.add(rollovers.get(i).toString()); } } return Joiner.on(" => ").join(l); case NONE: return "None"; default: return "???"; } } public String phaseString() { return rollovers.get(rolloverPhase).toString(); } public String phaseString(int phase) { return rollovers.get(phase).toString(); } /** * Process a clock tick. */ public void tick() { long offset = System.currentTimeMillis() - lastChecked; lastChecked = System.currentTimeMillis(); if (active) { elapsed += offset; if (controlType != ControlType.NONE) { remainingTime -= offset; } } } public RolloverPhase getCurrentPhase() { return rollovers.get(rolloverPhase); } /** * The player has made a move - adjust time control accordingly, and deactivate the clock. */ public void moveMade() { newPhase = false; switch (controlType) { case MOVE_IN: remainingTime = totalTime; break; case ROLLOVER: rolloverMovesMade++; Debugger.getInstance().debug("moves made = " + rolloverMovesMade + ", phase = " + rolloverPhase); Debugger.getInstance().debug("need " + rollovers.get(rolloverPhase).getMoves()); if (rolloverMovesMade == rollovers.get(rolloverPhase).getMoves()) { rolloverMovesMade = 0; rolloverPhase = (rolloverPhase + 1) % rollovers.size(); remainingTime += rollovers.get(rolloverPhase).getMinutes() * 60000; newPhase = true; } remainingTime += rollovers.get(rolloverPhase).getIncrement(); default: break; } setActive(false); } public class RolloverPhase { private long increment; // milliseconds private int moves; private int minutes; RolloverPhase(String spec) { String[] fields = spec.split("/"); switch (fields.length) { case 3: this.increment = Long.parseLong(fields[2]) * 1000; // fall through case 2: this.moves = Integer.parseInt(fields[0]); this.minutes = Integer.parseInt(fields[1]); break; default: throw new IllegalArgumentException("invalid rollover specification: " + spec); } } public long getIncrement() { return increment; } public int getMoves() { return moves; } public int getMinutes() { return minutes; } @Override public String toString() { StringBuilder s = new StringBuilder(); s.append(getMoves()).append("Mv / ").append(getMinutes()).append("m"); if (getIncrement() > 0) { s.append(" + ").append(getIncrement() / 1000).append("s"); } return s.toString(); } } }