package jrummikub.control; import static jrummikub.model.PlayerSettings.Type.*; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import jrummikub.control.turn.AIControl; import jrummikub.control.turn.HumanTurnControl; import jrummikub.control.turn.ITurnControl; import jrummikub.control.turn.TurnControlFactory; import jrummikub.control.turn.TurnMode; import jrummikub.model.Hand; import jrummikub.model.IHand; import jrummikub.model.IPlayer; import jrummikub.model.IRoundState; import jrummikub.model.ITable; import jrummikub.model.Player; import jrummikub.model.PlayerSettings; import jrummikub.model.Position; import jrummikub.model.Score; import jrummikub.model.Stone; import jrummikub.model.StoneSet; import jrummikub.util.Connection; import jrummikub.util.Event; import jrummikub.util.Event1; import jrummikub.util.IEvent; import jrummikub.util.IEvent1; import jrummikub.util.IListener; import jrummikub.util.IListener1; import jrummikub.util.Pair; import jrummikub.view.IView; import jrummikub.view.IView.BottomPanelType; /** * Controller that manages a single round of rummikub */ public class RoundControl { private ITurnControl turnControl; protected IRoundState roundState; private IView view; private ITable clonedTable; IHand clonedHand; private Event restartRoundEvent = new Event(); private Event1 roundStateUpdateEvent = new Event1(); private Event1 endOfRoundEvent = new Event1(); protected List connections = new ArrayList(); private boolean roundFinished; private boolean lastTurnNotEnoughPoints; private boolean lastTurnMeldError; /** * Create a new RoundControl using the given gameState and view * * @param roundState * initial round state * @param view * view used for user interaction */ public RoundControl(IRoundState roundState, IView view) { this.roundState = roundState; this.view = view; } public IEvent1 getRoundStateUpdateEvent() { return roundStateUpdateEvent; } /** * End the round * * @return endOfRoundEvent */ public IEvent1 getEndOfRoundEvent() { return endOfRoundEvent; } /** * Begin the round */ public void startRound() { if (roundState != null) { deal(); } continueRound(); } /** * Continue a saved round after loading */ public void continueRound() { connections.add(view.getStartTurnEvent().add(new IListener() { @Override public void handle() { startTurn(); } })); connections.add(view.getAcknowledgeInvalidEvent().add(new IListener() { @Override public void handle() { nextPlayer(); } })); if (roundState != null) { prepareTurn(); } } /** * Abort round if a new one is started or a saved one is loaded */ public void abortRound() { removeListeners(); if (turnControl != null) { turnControl.abortTurn(); turnControl = null; } } protected void setRoundState(IRoundState state) { roundState = state; roundStateUpdateEvent.emit(state); } protected void prepareTurn() { doPrepareTurn(); boolean isHuman = roundState.getActivePlayer().getPlayerSettings() .getType() == HUMAN; boolean oneHuman = roundState.getGameSettings().oneHuman(); boolean isAI = (turnControl instanceof AIControl); if (isAI || (isHuman && oneHuman)) { startTurn(); } } protected void doPrepareTurn() { updateSidePanel(); boolean isHuman = roundState.getActivePlayer().getPlayerSettings() .getType() == HUMAN; boolean oneHuman = roundState.getGameSettings().oneHuman(); clonedTable = (ITable) roundState.getTable().clone(); clonedHand = (IHand) roundState.getActivePlayer().getHand().clone(); if (isHuman) { view.setBottomPanel(oneHuman ? BottomPanelType.HUMAN_HAND_PANEL : BottomPanelType.START_TURN_PANEL); } else { view.setBottomPanel(BottomPanelType.NONHUMAN_HAND_PANEL); } view.getTablePanel().setStoneSets(clonedTable.clone()); view.setCurrentPlayerName(roundState.getActivePlayer() .getPlayerSettings().getName()); view.setCurrentPlayerColor(roundState.getActivePlayer() .getPlayerSettings().getColor()); view.setCurrentPlayerHasLaidOut(roundState.getActivePlayer() .getLaidOut()); turnControl = createTurnControl(roundState.getActivePlayer() .getPlayerSettings()); } protected void startTurn() { if (turnControl == null) { doPrepareTurn(); } boolean isHuman = roundState.getActivePlayer().getPlayerSettings() .getType() == HUMAN; TurnMode turnMode = TurnMode.NORMAL_TURN; if (roundState.getTurnNumber() < 1) { view.setStoneCollectionHidden(true); turnMode = TurnMode.INSPECT_ONLY; if (clonedHand.getIdenticalStoneCount() >= 3) { turnMode = TurnMode.MAY_REDEAL; } } if (isHuman) { view.getPlayerPanel().setEndTurnMode(turnMode); } turnControl.setup(new ITurnControl.TurnInfo(clonedTable, clonedHand, roundState.getActivePlayer().getLaidOut(), turnMode), roundState.getGameSettings(), view); turnControl.getEndOfTurnEvent().add(new IListener1() { @Override public void handle(ITable newTable) { endOfTurn(newTable); } }); turnControl.getRedealEvent().add(new IListener() { @Override public void handle() { redeal(); } }); addTurnControlListeners(turnControl); turnControl.startTurn(); } private void updateSidePanel() { view.showSidePanel(true); view.getSidePanel().setGameSettings(roundState.getGameSettings()); List players = new ArrayList(); for (int i = 1; i < roundState.getPlayerCount(); i++) { players.add(roundState.getNthNextPlayer(-i)); } view.getSidePanel().setPlayers(players); view.getSidePanel().setHeapCapacity( roundState.getGameSettings().getTotalStones()); view.getSidePanel().setHeapSize(roundState.getGameHeap().getSize()); } /** Override this */ protected void addTurnControlListeners(ITurnControl turnControl) { } protected ITurnControl createTurnControl(PlayerSettings playerSettings) { return TurnControlFactory.getFactory(playerSettings.getType()).create(); } void deal() { for (int i = 0; i < roundState.getPlayerCount(); i++) { IHand hand = roundState.getNthNextPlayer(i).getHand(); for (int j = 0; j < roundState.getGameSettings() .getNumberOfStonesDealt(); j++) { hand.drop(roundState.getGameHeap().drawStone(), new Position(0, 0)); } } } private boolean laidOutValidPoints() { List newSets = tableSetDifference(roundState.getTable(), clonedTable); int totalValue = 0; for (StoneSet set : newSets) { totalValue += set.classify(roundState.getGameSettings()) .getSecond(); } return totalValue == 0 || totalValue >= roundState.getGameSettings() .getInitialMeldThreshold(); } protected void endOfTurn(ITable newTable) { boolean wasHuman = (turnControl instanceof HumanTurnControl); boolean wasAI = (turnControl instanceof AIControl); turnControl = null; roundState.getActivePlayer().setLastTurnInvalid(false); roundState.getActivePlayer().setLastTurnStoneCount( roundState.getActivePlayer().getHand().getSize() - clonedHand.getSize()); roundState.getActivePlayer().setHand(clonedHand); boolean goToNextPlayer = true; lastTurnNotEnoughPoints = false; lastTurnMeldError = false; if (roundState.getTurnNumber() >= 1) { goToNextPlayer = checkTurn(newTable); } if (goToNextPlayer || wasAI) { nextPlayer(); } else { if (wasHuman) { view.setBottomPanel(BottomPanelType.INVALID_TURN_PANEL); } if (lastTurnNotEnoughPoints) { view.setInitialMeldError(roundState.getGameSettings() .getInitialMeldThreshold()); view.setInvalidStoneSets(tableSetDifference( roundState.getTable(), newTable)); } else if (lastTurnMeldError) { view.setInitialMeldFirstError(); view.setInvalidStoneSets(touchedStoneSets(newTable)); } else { view.setStoneCollectionHidden(true); view.setInvalidStoneSets(invalidStoneSets(newTable)); } } } private List invalidStoneSets(ITable newTable) { List invalidSets = new ArrayList(); for (Pair set : newTable) { if (set.getFirst().isValid(roundState.getGameSettings())) { continue; } invalidSets.add(set.getFirst()); } return invalidSets; } private List touchedStoneSets(ITable newTable) { List touchedSets = new ArrayList(); for (StoneSet set : tableSetDifference(roundState.getTable(), newTable)) { for (Stone stone : set) { if (!roundState.getActivePlayer().getHand().contains(stone)) { touchedSets.add(set); break; } } } return touchedSets; } private void nextPlayer() { view.setSelectedStones(Collections. emptyList()); view.setInvalidStoneSets(Collections. emptyList()); view.setStoneCollectionHidden(false); if (roundState.getLastPlayer() == null) { if (roundState.getGameHeap().getSize() == 0) { roundState.setLastPlayer(roundState.getNthNextPlayer(0)); roundState.nextPlayer(); roundState.nextTurn(); } else { roundState.nextPlayer(); roundState.nextTurn(); } } else { if (roundState.getActivePlayer() == roundState.getLastPlayer()) { endOfRound(); } else { roundState.nextPlayer(); roundState.nextTurn(); } } if (!roundFinished) { prepareTurn(); } } private boolean checkTurn(ITable newTable) { if (!newTable.isValid()) { rejectMove(newTable); return false; } if (!roundState.getActivePlayer().getLaidOut()) { // Player touched forbidden stones if (!tableSetDifference(newTable, roundState.getTable()).isEmpty()) { rejectMove(newTable); lastTurnMeldError = true; return false; } if (!laidOutValidPoints()) { rejectMove(newTable); lastTurnNotEnoughPoints = true; return false; } } Set tableDiff = tableDifference(roundState.getTable(), newTable); roundState.setTable(newTable); if (tableDiff.isEmpty()) { // Player hasn't made a move dealStone(); } else { roundState.getActivePlayer().setLaidOut(true); if (roundState.getActivePlayer().getHand().getSize() == 0) { endOfRound(); } } return true; } private void rejectMove(ITable newTable) { Set tableDiff = tableDifference(roundState.getTable(), newTable); // deal penalty, reset roundState.getGameHeap().putBack(tableDiff); roundState.getActivePlayer().setLastTurnInvalid(true); dealPenalty(tableDiff.size()); } static Set tableDifference(ITable oldTable, ITable newTable) { Set ret = new HashSet(); for (Pair entry : newTable) { for (Stone stone : entry.getFirst()) { ret.add(stone); } } for (Pair entry : oldTable) { for (Stone stone : entry.getFirst()) { ret.remove(stone); } } return ret; } static List tableSetDifference(ITable oldTable, ITable newTable) { List ret = new ArrayList(); for (Pair entry : newTable) { ret.add(entry.getFirst()); } for (Pair entry : oldTable) { ret.remove(entry.getFirst()); } return ret; } void dealStones(int count) { IHand hand = roundState.getActivePlayer().getHand(); int rowCount = hand.getRowCount(); for (int i = 0; i < count; ++i) { if (hand.getFreeRowSpace(rowCount - 1) == 0) { rowCount++; } hand.drop(roundState.getGameHeap().drawStone(), new Position( Hand.WIDTH - 1, rowCount - 1)); } } private void dealStone() { dealStones(1); } private void dealPenalty(int count) { dealStones(count + 3); } void endOfRound() { removeListeners(); Score roundScore = score(); endOfRoundEvent.emit(roundScore); roundFinished = true; } private void removeListeners() { for (Connection c : connections) { c.remove(); } } private Score score() { List winners = new ArrayList(); List points = new ArrayList(); boolean foundRegularWinner = false; int winnerPlayerNumber = 0; Pair bestScore = new Pair( Integer.MIN_VALUE, Integer.MAX_VALUE); int pointSum = 0; for (int i = 0; i < roundState.getPlayerCount(); i++) { IPlayer player = roundState.getNthPlayer(i); IHand playerHand = player.getHand(); boolean isWinner = playerHand.getSize() == 0; winners.add(isWinner); if (isWinner) { foundRegularWinner = true; winnerPlayerNumber = i; } int stonePoints = 0; if (!player.getLaidOut()) { stonePoints = playerHand.isInitialMeldPossible(roundState .getGameSettings()) ? 200 : 100; } else { stonePoints = playerHand.getStonePoints(roundState .getGameSettings()); } bestScore = updateBestScore(bestScore, -stonePoints, playerHand.getSize()); points.add(-stonePoints); pointSum += stonePoints; } if (foundRegularWinner) { points.set(winnerPlayerNumber, pointSum); } else { for (int i = 0; i < roundState.getPlayerCount(); i++) { if (bestScore.equals(new Pair(points.get(i), roundState.getNthPlayer(i).getHand().getSize()))) { winners.set(i, true); } } } return new Score(winners, points); } private static Pair updateBestScore( Pair bestScore, int stonePoints, int size) { if (bestScore.getFirst() == stonePoints) { return new Pair(stonePoints, Math.min( bestScore.getSecond(), size)); } else if (bestScore.getFirst() < stonePoints) { return new Pair(stonePoints, size); } return bestScore; } /** * Emitted when the round is aborted and needs to be restarted * * @return the event */ public IEvent getRestartRoundEvent() { return restartRoundEvent; } private void redeal() { turnControl = null; for (Connection c : new ArrayList(connections)) { c.remove(); } restartRoundEvent.emit(); } }