/** * Copyright (C) 2017 Jan Schäfer (jansch@users.sourceforge.net) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jskat.control; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import org.jskat.control.command.skatseries.ReplayGameCommand; import org.jskat.control.command.table.NextReplayMoveCommand; import org.jskat.control.event.skatgame.GameStartEvent; import org.jskat.control.event.table.SkatGameReplayStartedEvent; import org.jskat.data.SkatGameData.GameState; import org.jskat.data.SkatSeriesData; import org.jskat.data.SkatSeriesData.SeriesState; import org.jskat.gui.JSkatView; import org.jskat.player.JSkatPlayer; import org.jskat.util.GameVariant; import org.jskat.util.Player; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.eventbus.Subscribe; /** * Controls a series of skat games */ public class SkatSeries extends JSkatThread { private static final Logger LOG = LoggerFactory.getLogger(SkatSeries.class); private static final Random RANDOM = new Random(); private int maxSleep = 0; private final SkatSeriesData data; private int roundsToGo = 0; private boolean unlimitedRounds = false; private boolean onlyPlayRamsch = false; private final Map<Player, JSkatPlayer> players; private SkatGame currSkatGame; private SkatGameReplayer currReplayGame; private JSkatView view; /** * Constructor * * @param tableName * Table name */ public SkatSeries(final String tableName) { data = new SkatSeriesData(); data.setState(SeriesState.WAITING); data.setTableName(tableName); view = JSkatMaster.INSTANCE.getView(); JSkatEventBus.TABLE_EVENT_BUSSES.get(tableName).register(this); setName("SkatSeries on table " + tableName); //$NON-NLS-1$ players = new HashMap<Player, JSkatPlayer>(); } @Subscribe public void startReplayGameOn(final ReplayGameCommand command) throws InterruptedException { JSkatEventBus.TABLE_EVENT_BUSSES.get(data.getTableName()).post( new SkatGameReplayStartedEvent()); currReplayGame = new SkatGameReplayer(view, data.getTableName(), currSkatGame.getGameMoves()); } @Subscribe public void replayNextMoveOn(final NextReplayMoveCommand comman) { currReplayGame.oneMoveForward(); } /** * Sets the skat players * * @param newPlayers * New skat series player */ public void setPlayers(final List<JSkatPlayer> newPlayers) { if (newPlayers.size() != 3) { throw new IllegalArgumentException( "Only three players are allowed at the moment."); //$NON-NLS-1$ } view.setPlayerNames(data.getTableName(), newPlayers.get(0).getPlayerName(), newPlayers.get(0).isAIPlayer(), newPlayers.get(1).getPlayerName(), newPlayers.get(1).isAIPlayer(), newPlayers.get(2).getPlayerName(), newPlayers.get(2).isAIPlayer()); // memorize third player to find it again after shuffling the players final JSkatPlayer thirdPlayer = newPlayers.get(2); // set players in random order // simple Collection.shuffle doesn't work here, because the order of // players should be the same like in start skat series dialog final int startPlayer = RANDOM.nextInt(3); players.put(Player.FOREHAND, newPlayers.get(startPlayer)); players.put(Player.MIDDLEHAND, newPlayers.get((startPlayer + 1) % 3)); players.put(Player.REARHAND, newPlayers.get((startPlayer + 2) % 3)); // if an human player is playing, always show him/her at the bottom // FIXME (jansch 09.05.2012) this is GUI logic, move it to the GUI // package for (final Player hand : Player.values()) { if (players.get(hand).isHumanPlayer() || players.get(hand) == thirdPlayer) { data.setBottomPlayer(hand); } } LOG.debug("Player order: " + players); //$NON-NLS-1$ } /** * Checks whether a series is running * * @return TRUE if the series is running */ public boolean isRunning() { return SeriesState.RUNNING.equals(data.getState()); } /** * Starts the series * * @param rounds * Number of rounds to be played * @param newUnlimitedRound * TRUE, if the number of rounds is not limited */ public void setMaxRounds(final int rounds, final boolean newUnlimitedRound) { roundsToGo = rounds; unlimitedRounds = newUnlimitedRound; data.setState(SeriesState.RUNNING); } /** * @see Thread#run() */ @Override public void run() { int roundsPlayed = 0; int gameNumber = 0; while ((roundsToGo > 0 || unlimitedRounds) && !isTerminated()) { LOG.debug("Playing round " + (roundsPlayed + 1)); //$NON-NLS-1$ for (int j = 0; j < 3; j++) { if (j > 0 || roundsPlayed > 0) { // change player positions after first game final JSkatPlayer helper = players.get(Player.REARHAND); players.put(Player.REARHAND, players.get(Player.FOREHAND)); players.put(Player.FOREHAND, players.get(Player.MIDDLEHAND)); players.put(Player.MIDDLEHAND, helper); data.setBottomPlayer(data.getBottomPlayer() .getRightNeighbor()); } gameNumber++; GameVariant gameVariant = GameVariant.STANDARD; if (onlyPlayRamsch) { gameVariant = GameVariant.FORCED_RAMSCH; } currSkatGame = new SkatGame(data.getTableName(), gameVariant, players.get(Player.FOREHAND), players.get(Player.MIDDLEHAND), players.get(Player.REARHAND)); JSkatEventBus.TABLE_EVENT_BUSSES.get(data.getTableName()).post( new GameStartEvent(gameNumber, gameVariant, data .getBottomPlayer().getLeftNeighbor(), data .getBottomPlayer().getRightNeighbor(), data .getBottomPlayer())); currSkatGame.setView(view); currSkatGame.setMaxSleep(maxSleep); LOG.debug("Playing game " + (j + 1)); //$NON-NLS-1$ data.addGame(currSkatGame); currSkatGame.start(); try { currSkatGame.join(); LOG.debug("Game ended: join"); //$NON-NLS-1$ sleep(maxSleep); } catch (final InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (isHumanPlayerInvolved()) { // wait for human to start next game startWaiting(); } checkWaitCondition(); // Breaks on termination - // the thread has to come to the end if (isTerminated()) { break; } } roundsToGo--; roundsPlayed++; checkWaitCondition(); } data.setState(SeriesState.SERIES_FINISHED); view.setSeriesState(data.getTableName(), SeriesState.SERIES_FINISHED); LOG.debug(data.getState().name()); } private boolean isHumanPlayerInvolved() { boolean result = false; for (final JSkatPlayer currPlayer : players.values()) { if (currPlayer.isHumanPlayer()) { result = true; } } return result; } /** * Gets the state of the series * * @return State of the series */ public SeriesState getSeriesState() { return data.getState(); } /** * Gets the game state of the current game * * @return Game state */ public GameState getGameState() { return data.getGameState(); } /** * Gets the ID of the current game * * @return ID of the current game */ public int getCurrentGameID() { return data.getCurrentGameID(); } /** * Pauses the current game */ public void pauseSkatGame() { synchronized (currSkatGame) { currSkatGame.startWaiting(); } } /** * Resumes the current game */ public void resumeSkatGame() { synchronized (currSkatGame) { currSkatGame.stopWaiting(); currSkatGame.notify(); } } /** * Checks whether the current skat game is paused * * @return TRUE if the current skat game is paused */ public boolean isSkatGameWaiting() { return currSkatGame.isWaiting(); } /** * Sets the view for the series * * @param newView * View */ public void setView(final JSkatView newView) { view = newView; } /** * Sets whether only ramsch games are played or not * * @param isOnlyPlayRamsch * TRUE, if only ramsch games should be played */ public void setOnlyPlayRamsch(final boolean isOnlyPlayRamsch) { onlyPlayRamsch = isOnlyPlayRamsch; } /** * Sets max sleep between actions during the skat series, this must only be * set in skat series that are run with a GUI, otherwise the default value * of 0 is used * * @param newMaxSleep * New value for maximum sleep time in milliseconds */ public void setMaxSleep(final int newMaxSleep) { maxSleep = newMaxSleep; } }