package jrummikub.control.turn; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.swing.SwingUtilities; import javax.swing.Timer; import jrummikub.ai.TurnLogic; import jrummikub.model.Position; import jrummikub.model.Stone; import jrummikub.model.StoneSet; import jrummikub.util.Pair; /** * Base class for AI players * * Code not covered by tests uses timers and background threads. * */ public class AIControl extends AbstractTurnControl { private TurnLogic logic; /** * Does the AI control currently use an internal timer */ public static boolean useBackgroundThread = true; long startTime; private boolean isPaused = false; private boolean turnDone = false; private boolean readyToEmit = false; private boolean aborted = false; @Override protected void doStartTurn() { timer.startTimer(); startTime = System.currentTimeMillis(); compute(); } @Override protected void timeOut() { executeTurn(); } @Override protected void pauseTurn() { super.pauseTurn(); isPaused = true; } @Override protected void resumeTurn() { super.resumeTurn(); isPaused = false; if (readyToEmit) { emitEndOfTurn(); } } @Override protected void cleanUp() { if (logic != null) logic.abort(); turnDone = true; super.cleanUp(); } @Override public void abortTurn() { aborted = true; super.abortTurn(); } private void compute() { switch (turnInfo.getTurnMode()) { case MAY_REDEAL: endTurn(); break; case INSPECT_ONLY: endTurn(); break; case NORMAL_TURN: turn(); break; } } private void turn() { List tableStones = new ArrayList(); List handStones = new ArrayList(); addHandStones(handStones); addTableStones(tableStones); logic = new TurnLogic(settings, tableStones, handStones); if (!turnInfo.getLaidOut()) { logic.needIntialMeldThreshold(); } if (useBackgroundThread) { startTimers(); Thread computeThread = new Thread(new Runnable() { @Override public void run() { logic.optimize(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { executeTurn(); } }); } }); computeThread.start(); } else { logic.optimize(); executeTurn(); } } private void startTimers() { Timer timer = new Timer(10000, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { logic.autoAbort(); } }); timer.setRepeats(false); timer.start(); timer = new Timer((int) (turnInfo.getRoundState().getGameSettings() .getTotalTime() * 1000 * (0.5 + 0.25 * Math.random())), new ActionListener() { @Override public void actionPerformed(ActionEvent e) { executeTurn(); } }); timer.setRepeats(false); timer.start(); } private void addHandStones(List handStones) { for (Pair entry : turnInfo.getHand()) { handStones.add(entry.getFirst()); } } private void addTableStones(List tableStones) { if (turnInfo.getLaidOut()) { for (Pair entry : turnInfo.getTable()) { for (Stone stone : entry.getFirst()) { tableStones.add(stone); } } } } private void executeTurn() { if (turnDone) { return; } List result = logic.getResult(); if (result != null) { if (turnInfo.getLaidOut()) { doNotMoveExistingSets(result); } for (StoneSet set : result) { turnInfo.getTable().drop( set, new Position(10 * (Math.random() * 2 - 1), 5 * (Math.random() * 2 - 1))); for (Stone stone : set) { turnInfo.getHand().pickUp(stone); } } } endTurn(); } private void doNotMoveExistingSets(List result) { outerLoop: for (Iterator> it = turnInfo.getTable() .iterator(); it.hasNext();) { Pair pair = it.next(); setSearch: for (Iterator it2 = result.iterator(); it2.hasNext();) { StoneSet set = it2.next(); if (set.getSize() != pair.getFirst().getSize()) { continue; } for (int i = 0; i < set.getSize(); i++) { if (set.get(i) != pair.getFirst().get(i)) { continue setSearch; } } it2.remove(); continue outerLoop; } it.remove(); } } private void endTurn() { turnDone = true; long turnLength = System.currentTimeMillis() - startTime; if (useBackgroundThread) { Timer timer = new Timer(Math.max(0, (int) (1000 + Math.random() * 2000 - turnLength)), new ActionListener() { @Override public void actionPerformed(ActionEvent event) { emitEndOfTurn(); } }); timer.setRepeats(false); timer.start(); } else { emitEndOfTurn(); } } private void emitEndOfTurn() { readyToEmit = true; if (isPaused || aborted) { return; } cleanUp(); endOfTurnEvent.emit(turnInfo.getRoundState(), checkTurn()); } /** * Get the factory for the base AI control * * @return the factory */ static public TurnControlFactory getFactory() { return new TurnControlFactory() { @Override public ITurnControl create() { return new AIControl(); } }; } }