/** * 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 org.jskat.control.command.table.ShowCardsCommand; import org.jskat.control.event.skatgame.BidEvent; import org.jskat.control.event.skatgame.CardDealEvent; import org.jskat.control.event.skatgame.ContraEvent; import org.jskat.control.event.skatgame.GameAnnouncementEvent; import org.jskat.control.event.skatgame.GameFinishEvent; import org.jskat.control.event.skatgame.HoldBidEvent; import org.jskat.control.event.skatgame.PassBidEvent; import org.jskat.control.event.skatgame.ReEvent; import org.jskat.control.event.skatgame.SkatGameEvent; import org.jskat.control.event.skatgame.TrickCardPlayedEvent; import org.jskat.control.event.table.ActivePlayerChangedEvent; import org.jskat.control.event.table.TableGameMoveEvent; import org.jskat.control.event.table.TrickCompletedEvent; import org.jskat.data.GameAnnouncement; import org.jskat.data.GameAnnouncement.GameAnnouncementFactory; import org.jskat.data.GameSummary; import org.jskat.data.JSkatOptions; import org.jskat.data.SkatGameData; import org.jskat.data.SkatGameData.GameState; import org.jskat.data.SkatGameResult; import org.jskat.data.SkatTableOptions.ContraCallingTime; import org.jskat.data.SkatTableOptions.RamschSkatOwner; import org.jskat.data.Trick; import org.jskat.gui.JSkatView; import org.jskat.player.JSkatPlayer; import org.jskat.util.Card; import org.jskat.util.CardDeck; import org.jskat.util.CardList; import org.jskat.util.GameType; import org.jskat.util.GameVariant; import org.jskat.util.JSkatResourceBundle; import org.jskat.util.Player; import org.jskat.util.SkatConstants; import org.jskat.util.rule.SkatRule; import org.jskat.util.rule.SkatRuleFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Controls a skat game. */ public class SkatGame extends JSkatThread { private Logger log = LoggerFactory.getLogger(SkatGame.class); private int maxSleep; private final SkatGameData data; private final GameVariant variant; private CardDeck deck; private final Map<Player, JSkatPlayer> player; private Player activePlayer; private final String tableName; private JSkatView view; private SkatRule rules; private final JSkatResourceBundle strings = JSkatResourceBundle.INSTANCE; /** * Constructor * * @param newTableName * Table name * @param variant * game variant * @param newForeHand * Fore hand player * @param newMiddleHand * Middle hand player * @param newRearHand * Rear hand player */ public SkatGame(final String newTableName, final GameVariant variant, final JSkatPlayer newForeHand, final JSkatPlayer newMiddleHand, final JSkatPlayer newRearHand) { this.tableName = newTableName; setName("SkatGame on table " + this.tableName); //$NON-NLS-1$ data = new SkatGameData(); JSkatEventBus.TABLE_EVENT_BUSSES.get(tableName).register(data); this.variant = variant; player = new HashMap<Player, JSkatPlayer>(); player.put(Player.FOREHAND, newForeHand); player.put(Player.MIDDLEHAND, newMiddleHand); player.put(Player.REARHAND, newRearHand); // inform all players about the starting of the new game for (final Player pos : this.player.keySet()) { getPlayerInstance(pos).newGame(pos); } setGameState(GameState.GAME_START); } /** * @see Thread#run() */ // FIXME jan 11.07.2013: this method is too long, break it down to smaller // methods or implement it in another way @Override public void run() { this.view.setGameState(this.tableName, this.data.getGameState()); do { this.log.debug("SkatGame.do --- Game state: " + this.data.getGameState()); //$NON-NLS-1$ switch (this.data.getGameState()) { case GAME_START: setGameState(GameState.DEALING); break; case DEALING: dealCards(); setGameState(GameState.BIDDING); break; case BIDDING: setActivePlayer(Player.MIDDLEHAND); if (this.variant == GameVariant.FORCED_RAMSCH) { // ramsch games are enforced final GameAnnouncementFactory gaf = GameAnnouncement.getFactory(); gaf.setGameType(GameType.RAMSCH); setGameAnnouncement(gaf.getAnnouncement()); } else { // "normal" game (i.e. no ramsch) bidding(); } if (GameType.PASSED_IN.equals(this.data.getGameType())) { setGameState(GameState.PRELIMINARY_GAME_END); } else if (GameType.RAMSCH.equals(this.data.getGameType())) { setGameState(GameState.RAMSCH_GRAND_HAND_ANNOUNCING); } else { this.view.setDeclarer(this.tableName, this.data.getDeclarer()); setGameState(GameState.PICKING_UP_SKAT); } break; case RAMSCH_GRAND_HAND_ANNOUNCING: boolean grandHandAnnounced = grandHand(); if (grandHandAnnounced) { this.log.debug(this.data.getDeclarer() + " is playing grand hand"); //$NON-NLS-1$ final GameAnnouncementFactory gaf = GameAnnouncement.getFactory(); gaf.setGameType(GameType.GRAND); gaf.setHand(Boolean.TRUE); setGameAnnouncement(gaf.getAnnouncement()); setGameState(GameState.TRICK_PLAYING); this.log.debug("grand hand game started"); //$NON-NLS-1$ break; } else { if (JSkatOptions.instance().isSchieberamsch(true)) { this.log.debug("no grand hand - initiating schieberamsch"); //$NON-NLS-1$ setGameState(GameState.SCHIEBERAMSCH); } else { this.log.debug("no grand hand and no schieberamsch - play ramsch"); //$NON-NLS-1$ setGameState(GameState.TRICK_PLAYING); } } break; case SCHIEBERAMSCH: schieberamsch(); GameAnnouncementFactory factory = GameAnnouncement.getFactory(); factory.setGameType(GameType.RAMSCH); setGameAnnouncement(factory.getAnnouncement()); setGameState(GameState.TRICK_PLAYING); break; case PICKING_UP_SKAT: setActivePlayer(this.data.getDeclarer()); if (pickUpSkat()) { setGameState(GameState.DISCARDING); } else { setGameState(GameState.DECLARING); } break; case DISCARDING: setActivePlayer(this.data.getDeclarer()); discarding(); if (!GameState.PRELIMINARY_GAME_END.equals(this.data.getGameState())) { setGameState(GameState.DECLARING); } break; case DECLARING: announceGame(); if (isContraPlayEnabled(ContraCallingTime.AFTER_GAME_ANNOUNCEMENT, 0)) { setGameState(GameState.CONTRA); } else { setGameState(GameState.TRICK_PLAYING); } break; case CONTRA: for (Player player : Player.getOrderedList()) { if (isContraEnabledForPlayer(player, ContraCallingTime.AFTER_GAME_ANNOUNCEMENT, 0)) { setActivePlayer(player); contraRe(); } } setGameState(GameState.TRICK_PLAYING); break; case TRICK_PLAYING: playTricks(); setGameState(GameState.CALCULATING_GAME_VALUE); break; case PRELIMINARY_GAME_END: setGameState(GameState.CALCULATING_GAME_VALUE); break; case CALCULATING_GAME_VALUE: calculateGameValue(); setGameState(GameState.GAME_OVER); break; case GAME_OVER: break; } checkWaitCondition(); } while (this.data.getGameState() != GameState.GAME_OVER && !isTerminated()); JSkatEventBus.TABLE_EVENT_BUSSES.get(tableName).unregister(data); log.debug(data.getGameState().name()); log.debug("Game moves:"); for (SkatGameEvent event : data.getGameMoves()) { log.debug(event.toString()); } } private void contraRe() { if (getActivePlayerInstance().callContra()) { JSkatEventBus.INSTANCE.post(new TableGameMoveEvent(tableName, new ContraEvent(activePlayer))); setGameState(GameState.RE); Player activePlayerBeforeContraRe = activePlayer; setActivePlayer(this.data.getDeclarer()); if (getActivePlayerInstance().callRe()) { JSkatEventBus.INSTANCE.post(new TableGameMoveEvent(tableName, new ReEvent(activePlayer))); } setActivePlayer(activePlayerBeforeContraRe); } } private boolean grandHand() { boolean grandHandAnnounced = false; for (final Player currPlayer : Player.getOrderedList()) { setActivePlayer(currPlayer); if (!grandHandAnnounced && playGrandHand()) { this.log.debug("Player " + this.activePlayer + " is playing grand hand."); setDeclarer(this.activePlayer); grandHandAnnounced = true; } else { this.log.debug("Player " + this.activePlayer + " doesn't want to play grand hand."); } } return grandHandAnnounced; } private void schieberamsch() { for (final Player currPlayer : Player.getOrderedList()) { setActivePlayer(currPlayer); if (!pickUpSkat()) { this.log.debug("Player " + currPlayer + " does schieben."); //$NON-NLS-1$ this.data.addGeschoben(); this.view.setGeschoben(this.tableName, this.activePlayer); } else { this.log.debug("Player " + currPlayer + " wants to look into skat."); this.view.setSkat(this.tableName, this.data.getSkat()); discarding(); } } } private void setActivePlayer(final Player newPlayer) { this.activePlayer = newPlayer; JSkatEventBus.INSTANCE.post(new ActivePlayerChangedEvent(this.tableName, this.activePlayer)); } private boolean playGrandHand() { return getActivePlayerInstance().playGrandHand(); } private boolean pickUpSkat() { return getActivePlayerInstance().pickUpSkat(); } /** * Deals the cards to the players and the skat */ public void dealCards() { Map<Player, CardList> dealtCards = new HashMap<>(); for (Player player : Player.values()) { dealtCards.put(player, new CardList()); } if (this.deck == null) { // Skat game has no cards, yet this.deck = new CardDeck(); this.log.debug("shuffling..."); //$NON-NLS-1$ this.deck.shuffle(); this.log.debug(this.deck.toString()); } doSleep(this.maxSleep); this.log.debug("dealing..."); //$NON-NLS-1$ // deal three rounds of cards // deal three cards dealCards(3, dealtCards); // and put two cards into the skat CardList skat = new CardList(this.deck.remove(0), this.deck.remove(0)); // deal four cards dealCards(4, dealtCards); // deal three cards dealCards(3, dealtCards); JSkatEventBus.INSTANCE.post(new TableGameMoveEvent(tableName, new CardDealEvent(dealtCards, skat))); doSleep(this.maxSleep); this.log.warn("Fore hand: " + this.data.getPlayerCards(Player.FOREHAND)); //$NON-NLS-1$ this.log.warn("Middle hand: " //$NON-NLS-1$ + this.data.getPlayerCards(Player.MIDDLEHAND)); this.log.warn("Rear hand: " + this.data.getPlayerCards(Player.REARHAND)); //$NON-NLS-1$ this.log.warn("Skat: " + this.data.getSkat()); //$NON-NLS-1$ } /** * Deals a given number of cards to the players * * @param cardCount * Number of cards to be dealt to a player */ private void dealCards(final int cardCount, Map<Player, CardList> dealtCards) { for (final Player hand : Player.getOrderedList()) { CardList cards = new CardList(); for (int j = 0; j < cardCount; j++) { // deal amount of cards cards.add(this.deck.remove(0)); } // player can get original card object because Card is immutable getPlayerInstance(hand).takeCards(cards); dealtCards.get(hand).addAll(cards); } } /** * Controls the bidding of all players */ private void bidding() { int bidValue = 0; this.log.debug("ask middle and fore hand..."); //$NON-NLS-1$ bidValue = twoPlayerBidding(Player.MIDDLEHAND, Player.FOREHAND, bidValue); this.log.debug("Bid value after first bidding: " //$NON-NLS-1$ + bidValue); final Player firstWinner = getBiddingWinner(Player.MIDDLEHAND, Player.FOREHAND); this.log.debug("First bidding winner: " + firstWinner); //$NON-NLS-1$ this.log.debug("ask rear hand and first winner..."); //$NON-NLS-1$ bidValue = twoPlayerBidding(Player.REARHAND, firstWinner, bidValue); this.log.debug("Bid value after second bidding: " //$NON-NLS-1$ + bidValue); // get second winner Player secondWinner = getBiddingWinner(Player.REARHAND, firstWinner); if (secondWinner == Player.FOREHAND && bidValue == 0) { this.log.debug("Check whether fore hand holds at least one bid"); //$NON-NLS-1$ setActivePlayer(Player.FOREHAND); // check whether fore hand holds at least one bid if (getPlayerInstance(Player.FOREHAND).bidMore(18) > -1) { this.log.debug("Fore hand holds 18"); //$NON-NLS-1$ JSkatEventBus.INSTANCE.post(new TableGameMoveEvent(this.tableName, new BidEvent(secondWinner, 18))); } else { this.log.debug("Fore hand passes too"); //$NON-NLS-1$ JSkatEventBus.INSTANCE.post(new TableGameMoveEvent(this.tableName, new PassBidEvent(Player.FOREHAND))); secondWinner = null; } } if (secondWinner != null) { // there is a winner of the bidding setDeclarer(secondWinner); setActivePlayer(secondWinner); this.log.debug("Player " + this.data.getDeclarer() //$NON-NLS-1$ + " wins the bidding."); //$NON-NLS-1$ } else { // FIXME (jansch 02.01.2012) use cloned rule options here (see // MantisBT: 0000037) final JSkatOptions options = JSkatOptions.instance(); if (options.isPlayRamsch() && options.isRamschEventNoBid()) { this.log.debug("Playing ramsch due to no bid"); //$NON-NLS-1$ final GameAnnouncementFactory factory = GameAnnouncement.getFactory(); factory.setGameType(GameType.RAMSCH); setGameAnnouncement(factory.getAnnouncement()); JSkatEventBus.INSTANCE.post(new TableGameMoveEvent(tableName, new GameAnnouncementEvent(data.getDeclarer(), data.getAnnoucement()))); setActivePlayer(Player.FOREHAND); // do not call "setGameAnnouncement(..)" here! } else { // pass in final GameAnnouncementFactory factory = GameAnnouncement.getFactory(); factory.setGameType(GameType.PASSED_IN); setGameAnnouncement(factory.getAnnouncement()); } } doSleep(this.maxSleep); } private void informPlayersAboutBid(final Player bidPlayer, final int bidValue) { // inform all players about the last bid for (final JSkatPlayer playerInstance : this.player.values()) { playerInstance.bidByPlayer(bidPlayer, bidValue); } } /** * Controls the bidding between two players * * @param announcer * Announcing player * @param hearer * Hearing player * @param startBidValue * Bid value to start from * @return the final bid value */ private int twoPlayerBidding(final Player announcer, final Player hearer, final int startBidValue) { int currBidValue = startBidValue; boolean announcerPassed = false; boolean hearerPassed = false; while (!announcerPassed && !hearerPassed) { // get bid value final int nextBidValue = SkatConstants.getNextBidValue(currBidValue); this.view.setBidValueToMake(this.tableName, nextBidValue); // ask player setActivePlayer(announcer); final int announcerBidValue = getPlayerInstance(announcer).bidMore(nextBidValue); if (announcerBidValue > -1 && SkatConstants.bidOrder.contains(Integer.valueOf(announcerBidValue))) { this.log.debug("announcer bids " + announcerBidValue); //$NON-NLS-1$ // announcing hand holds bid currBidValue = announcerBidValue; this.data.addPlayerBid(announcer, announcerBidValue); informPlayersAboutBid(announcer, announcerBidValue); JSkatEventBus.INSTANCE .post(new TableGameMoveEvent(this.tableName, new BidEvent(announcer, announcerBidValue))); setActivePlayer(hearer); if (getPlayerInstance(hearer).holdBid(currBidValue)) { this.log.debug("hearer holds " + currBidValue); //$NON-NLS-1$ // hearing hand holds bid this.data.addPlayerBid(hearer, announcerBidValue); informPlayersAboutBid(hearer, announcerBidValue); JSkatEventBus.INSTANCE .post(new TableGameMoveEvent(this.tableName, new HoldBidEvent(hearer, announcerBidValue))); } else { this.log.debug("hearer passed at " + announcerBidValue); //$NON-NLS-1$ // hearing hand passed hearerPassed = true; this.data.setPlayerPass(hearer, true); JSkatEventBus.INSTANCE.post(new TableGameMoveEvent(this.tableName, new PassBidEvent(hearer))); } } else { this.log.debug("announcer passed at " + nextBidValue); //$NON-NLS-1$ // announcing hand passes announcerPassed = true; this.data.setPlayerPass(announcer, true); JSkatEventBus.INSTANCE.post(new TableGameMoveEvent(this.tableName, new PassBidEvent(announcer))); } } return currBidValue; } private Player getBiddingWinner(final Player announcer, final Player hearer) { Player biddingWinner = null; if (this.data.isPlayerPass(announcer)) { biddingWinner = hearer; } else if (this.data.isPlayerPass(hearer)) { biddingWinner = announcer; } return biddingWinner; } private void discarding() { final JSkatPlayer activePlayerInstance = getActivePlayerInstance(); this.view.setSkat(this.tableName, this.data.getSkat()); this.log.debug("Player " + this.activePlayer + " looks into the skat..."); //$NON-NLS-1$ //$NON-NLS-2$ this.log.debug("Skat before discarding: " + this.data.getSkat()); //$NON-NLS-1$ final CardList skatBefore = new CardList(this.data.getSkat()); // create a clone of the skat before sending it to the player // otherwise the player could change the skat after discarding activePlayerInstance.takeSkat(skatBefore); this.data.addSkatToPlayer(this.activePlayer); // ask player for the cards to be discarded // cloning is done to prevent the player // from manipulating the skat afterwards final CardList discardedSkat = new CardList(); discardedSkat.addAll(activePlayerInstance.discardSkat()); if (!checkDiscardedCards(this.activePlayer, discardedSkat)) { this.view.showAIPlayedSchwarzMessageDiscarding(activePlayerInstance.getPlayerName(), discardedSkat); endGameBecauseOfSchwarzPlaying(this.activePlayer); } else { this.log.debug("Discarded cards: " + discardedSkat); //$NON-NLS-1$ this.data.setDiscardedSkat(this.activePlayer, discardedSkat); if (!activePlayerInstance.isHumanPlayer()) { // human player has changed the cards in the GUI already this.view.setDiscardedSkat(this.tableName, this.activePlayer, skatBefore, discardedSkat); } } } private boolean checkDiscardedCards(Player player, CardList discardedSkat) { // TODO move this to skat rules? boolean result = true; if (discardedSkat == null) { this.log.error("Player is fooling!!! Skat is empty!"); //$NON-NLS-1$ result = false; } else if (discardedSkat.size() != 2) { this.log.error("Player is fooling!!! Skat doesn't have two cards!"); //$NON-NLS-1$ result = false; } else if (discardedSkat.get(0) == discardedSkat.get(1)) { this.log.error("Player is fooling!!! Skat cards are identical!"); //$NON-NLS-1$ result = false; } else if (!playerHasCard(player, discardedSkat.get(0)) || !playerHasCard(player, discardedSkat.get(1))) { this.log.error("Player is fooling!!! Player doesn't have had discarded card! Dis"); //$NON-NLS-1$ result = false; } // TODO check for jacks in the discarded skat in ramsch games return result; } private void announceGame() { this.log.debug("declaring game..."); //$NON-NLS-1$ // TODO check for valid game announcements final GameAnnouncement ann = getPlayerInstance(this.data.getDeclarer()).announceGame(); if (ann != null) { setGameAnnouncement(ann); } else { this.view.showErrorMessage(this.strings.getString("invalid_game_announcement_title"), //$NON-NLS-1$ this.strings.getString("invalid_game_announcement_message", ann)); //$NON-NLS-1$ } doSleep(this.maxSleep); } private void playTricks() { for (int trickNo = 0; trickNo < 10; trickNo++) { this.log.debug("=============== Play trick " + (trickNo + 1) + " ==============="); //$NON-NLS-1$ //$NON-NLS-2$ doSleep(this.maxSleep); Player trickForehand = getTrickForeHand(trickNo); setActivePlayer(trickForehand); informPlayersAboutNewTrick(trickNo, trickForehand); // Ask players for their cards this.log.debug("fore hand plays"); //$NON-NLS-1$ if (isContraEnabledForPlayer(this.activePlayer, ContraCallingTime.BEFORE_FIRST_CARD, trickNo)) { setGameState(GameState.CONTRA); contraRe(); } setGameState(GameState.TRICK_PLAYING); playCard(trickForehand, null, activePlayer); if (data.isGameFinished()) { break; } doSleep(this.maxSleep); this.log.debug("middle hand plays"); //$NON-NLS-1$ setActivePlayer(this.activePlayer.getLeftNeighbor()); if (isContraEnabledForPlayer(this.activePlayer, ContraCallingTime.BEFORE_FIRST_CARD, trickNo)) { setGameState(GameState.CONTRA); contraRe(); } setGameState(GameState.TRICK_PLAYING); playCard(trickForehand, data.getCurrentTrick().getFirstCard(), activePlayer); if (data.isGameFinished()) { break; } doSleep(this.maxSleep); this.log.debug("rear hand plays"); //$NON-NLS-1$ setActivePlayer(this.activePlayer.getLeftNeighbor()); if (isContraEnabledForPlayer(activePlayer, ContraCallingTime.BEFORE_FIRST_CARD, trickNo)) { setGameState(GameState.CONTRA); contraRe(); } setGameState(GameState.TRICK_PLAYING); playCard(trickForehand, data.getCurrentTrick().getFirstCard(), activePlayer); if (data.isGameFinished()) { break; } doSleep(this.maxSleep); Trick lastTrick = data.getLastCompletedTrick(); this.data.addPlayerPoints(lastTrick.getTrickWinner(), lastTrick.getValue()); informPlayersAboutCompletedTrick(lastTrick); // Check for preliminary ending of a null game if (GameType.NULL.equals(this.data.getGameType())) { if (lastTrick.getTrickWinner() == this.data.getDeclarer()) { // declarer has won a trick setGameState(GameState.PRELIMINARY_GAME_END); } } this.log.debug("Trick cards: " + lastTrick.getCardList()); //$NON-NLS-1$ logPlayerPoints(); if (getActivePlayerInstance().isAIPlayer()) { doSleep(JSkatOptions.instance().getWaitTimeAfterTrick() * 1000); } if (data.isGameFinished()) { break; } checkWaitCondition(); } addSkatPointsToPlayerPoints(); // set schneider/schwarz/jungfrau/durchmarsch flags switch (this.data.getGameType()) { case CLUBS: case SPADES: case HEARTS: case DIAMONDS: case GRAND: this.data.setSchneiderSchwarz(); break; case RAMSCH: this.data.setJungfrauDurchmarsch(); break; case NULL: case PASSED_IN: // do nothing break; } } private Boolean isContraPlayEnabled(ContraCallingTime gameTime, int trickNo) { JSkatOptions options = JSkatOptions.instance(); if (!GameVariant.FORCED_RAMSCH.equals(this.variant) && options.isPlayContra(true) && options.getContraCallingTime() == gameTime && isGameWithDeclarer()) { if (ContraCallingTime.AFTER_GAME_ANNOUNCEMENT == gameTime) { return true; } else if (ContraCallingTime.BEFORE_FIRST_CARD == gameTime && trickNo == 0) { return true; } } return false; } private Boolean isGameWithDeclarer() { GameType gameType = this.data.getGameType(); if (gameType == GameType.CLUBS || gameType == GameType.SPADES || gameType == GameType.HEARTS || gameType == GameType.DIAMONDS || gameType == GameType.GRAND || gameType == GameType.NULL) { return true; } return false; } private Boolean isContraEnabledForPlayer(Player player, ContraCallingTime gameTime, int trickNo) { if (isContraPlayEnabled(gameTime, trickNo) && isNoContraCalledYet() && isPlayerOpponent(player) && isPlayerBidHighEnoughForContra(player)) { return true; } return false; } private boolean isPlayerBidHighEnoughForContra(Player player) { JSkatOptions options = JSkatOptions.instance(); if (options.isContraAfterBid18() && this.data.getMaxPlayerBid(player) > 0) { return true; } return true; } private boolean isPlayerOpponent(Player player) { return player != this.data.getDeclarer(); } private boolean isNoContraCalledYet() { return !this.data.isContra(); } private void informPlayersAboutCompletedTrick(final Trick trick) { for (Player currPosition : Player.getOrderedList()) { getPlayerInstance(currPosition).showTrick((Trick) trick.clone()); } } private void informPlayersAboutNewTrick(int trickNo, Player trickForehand) { for (Player currPosition : Player.getOrderedList()) { getPlayerInstance(currPosition).newTrick(trickNo, trickForehand); } } private Player getTrickForeHand(int currentTrickNo) { Player trickForeHand = null; if (currentTrickNo == 0) { // first trick trickForeHand = Player.FOREHAND; } else { // get last trick winner as fore hand of next trick trickForeHand = this.data.getTrickWinner(currentTrickNo - 1); } return trickForeHand; } private void logPlayerPoints() { this.log.debug("Points: forehand: " + this.data.getPlayerPoints(Player.FOREHAND) + //$NON-NLS-1$ " middlehand: " //$NON-NLS-1$ + this.data.getPlayerPoints(Player.MIDDLEHAND) + " rearhand: " //$NON-NLS-1$ + this.data.getPlayerPoints(Player.REARHAND)); } private void addSkatPointsToPlayerPoints() { this.log.debug("Skat: " + this.data.getSkat()); if (this.data.getGameType() == GameType.RAMSCH) { addSkatPointsToPlayerPointsInRamschGames(); } else { // for all the other games, points to the declarer this.data.addPlayerPoints(this.data.getDeclarer(), this.data.getSkat().getTotalValue()); } logPlayerPoints(); } private void addSkatPointsToPlayerPointsInRamschGames() { if (JSkatOptions.instance().getRamschSkatOwner() == RamschSkatOwner.LAST_TRICK) { try { Player lastTrickWinner = data.getLastTrickWinner(); if (lastTrickWinner != null) { log.debug("Skat cards (" + data.getSkat().getTotalValue() + " points) are added to player @ " //$NON-NLS-1$ //$NON-NLS-2$ + lastTrickWinner + " (= last trick)"); //$NON-NLS-1$ data.addPlayerPoints(lastTrickWinner, data.getSkat().getTotalValue()); } } catch (IllegalArgumentException exception) { // IllegalArgumentException can be thrown if a game was ended // preliminary by a player playing Schwarz this.log.warn("Skat cards cannot be added to winner of final trick - trick winner is unknown"); //$NON-NLS-1$ } } else if (JSkatOptions.instance().getRamschSkatOwner() == RamschSkatOwner.LOSER) { int maxPoints = -1; Player looser = null; for (Player player : Player.values()) { int playerPoints = data.getPlayerPoints(player); if (playerPoints > maxPoints) { maxPoints = playerPoints; looser = player; } } data.addPlayerPoints(looser, data.getSkat().getTotalValue()); } } private void playCard(Player trickForeHand, Card firstTrickCard, Player currPlayer) { Card playedCard = null; final JSkatPlayer skatPlayer = getActivePlayerInstance(); boolean cardAccepted = false; boolean aiPlayerPlayedSchwarz = false; while (!cardAccepted && !aiPlayerPlayedSchwarz) { try { // ask player for the next card playedCard = skatPlayer.playCard(); } catch (final Exception exp) { this.log.error("Exception thrown by player " + skatPlayer + " playing " + currPlayer + ": " + exp); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ if (!skatPlayer.isHumanPlayer()) { aiPlayerPlayedSchwarz = true; } } this.log.debug(playedCard + " " + this.data); //$NON-NLS-1$ if (isCardSchwarzPlay(skatPlayer, currPlayer, firstTrickCard, playedCard)) { if (skatPlayer.isHumanPlayer()) { this.view.showCardNotAllowedMessage(playedCard); } else { this.view.showAIPlayedSchwarzMessageCardPlay(skatPlayer.getPlayerName(), playedCard); aiPlayerPlayedSchwarz = true; } } else { cardAccepted = true; } } if (playedCard != null) { // TODO: code duplication with SkatGameReplayer.oneStepForward() if (data.getCurrentTrick() != null && data.getCurrentTrick().getFirstCard() == null) { JSkatEventBus.TABLE_EVENT_BUSSES.get(tableName) .post(new TrickCompletedEvent(data.getLastCompletedTrick())); } JSkatEventBus.INSTANCE .post(new TableGameMoveEvent(this.tableName, new TrickCardPlayedEvent(currPlayer, playedCard))); for (final JSkatPlayer playerInstance : this.player.values()) { // inform all players // cloning of card is not neccessary, because Card is immutable playerInstance.cardPlayed(currPlayer, playedCard); } this.log.debug("playing card " + playedCard); //$NON-NLS-1$ } if (aiPlayerPlayedSchwarz) { // end game immediately endGameBecauseOfSchwarzPlaying(currPlayer); } } private void endGameBecauseOfSchwarzPlaying(Player currentPlayer) { this.data.getResult().setSchwarz(true); if (this.data.getDeclarer().equals(currentPlayer)) { // declarer played schwarz this.data.getResult().setWon(false); } else { // opponent played schwarz this.data.getResult().setWon(true); } this.data.setGameState(GameState.PRELIMINARY_GAME_END); } private boolean isCardSchwarzPlay(JSkatPlayer skatPlayer, Player position, Card firstTrickCard, Card playedCard) { boolean isSchwarz = false; if (playedCard == null) { this.log.error("Player is fooling!!! Did not play a card!"); //$NON-NLS-1$ isSchwarz = true; } else if (!playerHasCard(position, playedCard)) { this.log.error("Player (" + skatPlayer + ") is fooling!!! Doesn't have card " + playedCard + "!"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ isSchwarz = true; } else if (!rules.isCardAllowed(data.getGameType(), firstTrickCard, data.getPlayerCards(position), playedCard)) { this.log.error( "Player " + skatPlayer.getClass().toString() + " card not allowed: " + playedCard + " game type: " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + data.getGameType() + " first trick card: " //$NON-NLS-1$ + firstTrickCard + " player cards: " //$NON-NLS-1$ + data.getPlayerCards(position)); isSchwarz = true; } return isSchwarz; } private JSkatPlayer getActivePlayerInstance() { return this.player.get(this.activePlayer); } private JSkatPlayer getPlayerInstance(Player position) { return this.player.get(position); } /** * Checks whether a player has the card on it's hand or not * * @param card * Card to check * @return TRUE if the card is on player's hand */ private boolean playerHasCard(final Player player, final Card card) { boolean result = false; this.log.debug("Player " + player + " has card: player cards: " + this.data.getPlayerCards(player) //$NON-NLS-1$ + " card to check: " + card); for (final Card handCard : this.data.getPlayerCards(player)) { if (handCard.equals(card)) { result = true; } } return result; } private void calculateGameValue() { this.log.debug("Calculate game value"); //$NON-NLS-1$ // FIXME (jan 07.12.2010) don't let a data class calculate it's values this.data.calcResult(); this.log.debug("game value=" + this.data.getResult() + ", bid value=" //$NON-NLS-1$ //$NON-NLS-2$ + this.data.getMaxBidValue()); this.log.debug("Final game result: lost:" + this.data.isGameLost() + //$NON-NLS-1$ " game value: " + this.data.getResult()); //$NON-NLS-1$ this.log.debug("Final result: " + this.data.getDeclarerScore() + "/" + this.data.getOpponentScore()); for (final JSkatPlayer playerInstance : this.player.values()) { playerInstance.setGameSummary(this.data.getGameSummary()); playerInstance.finalizeGame(); } doSleep(this.maxSleep); } private void doSleep(final int milliseconds) { try { sleep(milliseconds); } catch (final InterruptedException e) { this.log.warn("sleep was interrupted..."); //$NON-NLS-1$ } } /** * Sets the view for the game * * @param newView * View * * @deprecated don't rely on setting a view anymore as we want to use event * busses now. */ @Deprecated public void setView(final JSkatView newView) { this.view = newView; } /** * Sets a new logger for the skat game * * @param newLogger * New logger */ public void setLogger(final Logger newLogger) { this.log = newLogger; } /** * Sets the cards from outside * * @param newDeck * Card deck */ public void setCardDeck(final CardDeck newDeck) { this.deck = newDeck; } /** * Sets the game announcement from the outside * * @param ann * Game announcement */ public void setGameAnnouncement(final GameAnnouncement ann) { this.data.setAnnouncement(ann); this.rules = SkatRuleFactory.getSkatRules(this.data.getGameType()); JSkatEventBus.INSTANCE .post(new TableGameMoveEvent(tableName, new GameAnnouncementEvent(data.getDeclarer(), ann))); // inform all players for (final JSkatPlayer playerInstance : this.player.values()) { playerInstance.startGame(this.data.getDeclarer(), ann); } this.log.debug(".setGameAnnouncement(): " + this.data.getAnnoucement() + " by " + this.data.getDeclarer() //$NON-NLS-1$ //$NON-NLS-2$ + ", rules=" + this.rules); //$NON-NLS-1$ } /** * Sets the game state from outside * * @param newState * Game state */ public void setGameState(final GameState newState) { this.data.setGameState(newState); if (this.view != null) { this.view.setGameState(this.tableName, newState); if (newState == GameState.GAME_OVER) { // FIXME: merge this event with the command JSkatEventBus.INSTANCE.post(new TableGameMoveEvent(tableName, new GameFinishEvent(getGameSummary()))); JSkatEventBus.INSTANCE.post(new ShowCardsCommand(tableName, data.getCardsAfterDiscard())); } } } /** * Sets the single player from outside * * @param declarer * Declarer */ public void setDeclarer(final Player declarer) { this.data.setDeclarer(declarer); this.view.setDeclarer(this.tableName, declarer); } /** * Gets the single player * * @return Single player */ public Player getDeclarer() { return this.data.getDeclarer(); } /** * Gets whether a game was won or not * * @return TRUE if the game was won */ public boolean isGameWon() { return this.data.isGameWon(); } /** * Gets the maximum sleep time * * @return Maximum sleep time */ public int getMaxSleep() { return this.maxSleep; } /** * Sets the maximum sleep time * * @param newMaxSleep * Maximum sleep time */ public void setMaxSleep(final int newMaxSleep) { this.maxSleep = newMaxSleep; } /** * Gets the game result * * @return Game result */ public SkatGameResult getGameResult() { return this.data.getGameResult(); } /** * Gets the game moves. * * @return List of game moves */ public List<SkatGameEvent> getGameMoves() { return this.data.getGameMoves(); } /** * Gets a summary of the game * * @return Game summary */ public GameSummary getGameSummary() { return this.data.getGameSummary(); } /** * @see Object#toString() */ @Override public String toString() { return this.data.getGameState().toString(); } /** * Gets the game announcement * * @return Game announcement */ public GameAnnouncement getGameAnnouncement() { return this.data.getAnnoucement(); } /** * Gets the game state * * @return Game state */ public GameState getGameState() { return this.data.getGameState(); } }