From 7fd9b51cabeaac9f783efcfbb7f54431460eb924 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 12 Apr 2005 02:50:17 +0000 Subject: [PATCH] First checkin for the great Spades refactor. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3465 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../card/client/CardGameController.java | 33 +- .../parlor/card/client/CardGameDecoder.java | 20 + .../parlor/card/client/CardGameReceiver.java | 20 + .../parlor/card/client/CardGameService.java | 26 - .../parlor/card/client/CardPanel.java | 1074 +++++++++++++++-- .../parlor/card/client/CardSprite.java | 219 +++- .../parlor/card/data/CardGameObject.java | 25 - .../com/threerings/parlor/card/data/Deck.java | 54 +- .../com/threerings/parlor/card/data/Hand.java | 64 +- .../PlayerCard.java} | 43 +- .../parlor/card/server/CardGameManager.java | 106 +- .../parlor/card/server/CardGameProvider.java | 30 - .../parlor/card/server/CardGameSender.java | 24 + .../TrickCardGameControllerDelegate.java | 29 +- .../trick/client/TrickCardGameService.java | 32 + .../TrickCardCodes.java} | 45 +- .../data/TrickCardGameMarshaller.java} | 32 +- .../card/trick/data/TrickCardGameObject.java | 115 +- .../server/TrickCardGameDispatcher.java} | 28 +- .../server/TrickCardGameManagerDelegate.java | 472 +++++++- .../trick/server/TrickCardGameProvider.java | 36 + .../card/trick/util/TrickCardGameUtil.java | 167 +++ 22 files changed, 2197 insertions(+), 497 deletions(-) delete mode 100644 src/java/com/threerings/parlor/card/client/CardGameService.java rename src/java/com/threerings/parlor/card/{trick/client/TrickCardGameController.java => data/PlayerCard.java} (56%) delete mode 100644 src/java/com/threerings/parlor/card/server/CardGameProvider.java create mode 100644 src/java/com/threerings/parlor/card/trick/client/TrickCardGameService.java rename src/java/com/threerings/parlor/card/trick/{server/TrickCardGameManager.java => data/TrickCardCodes.java} (51%) rename src/java/com/threerings/parlor/card/{data/CardGameMarshaller.java => trick/data/TrickCardGameMarshaller.java} (70%) rename src/java/com/threerings/parlor/card/{server/CardGameDispatcher.java => trick/server/TrickCardGameDispatcher.java} (69%) create mode 100644 src/java/com/threerings/parlor/card/trick/server/TrickCardGameProvider.java create mode 100644 src/java/com/threerings/parlor/card/trick/util/TrickCardGameUtil.java diff --git a/src/java/com/threerings/parlor/card/client/CardGameController.java b/src/java/com/threerings/parlor/card/client/CardGameController.java index 95908d3af..9dae4d13a 100644 --- a/src/java/com/threerings/parlor/card/client/CardGameController.java +++ b/src/java/com/threerings/parlor/card/client/CardGameController.java @@ -21,20 +21,22 @@ package com.threerings.parlor.card.client; +import com.threerings.util.Name; + import com.threerings.crowd.data.PlaceObject; import com.threerings.parlor.card.data.Card; import com.threerings.parlor.card.data.CardCodes; import com.threerings.parlor.card.data.Hand; - import com.threerings.parlor.game.client.GameController; +import com.threerings.parlor.turn.client.TurnGameController; /** * A controller class for card games. Handles common functions like * accepting dealt hands. */ public abstract class CardGameController extends GameController - implements CardCodes, CardGameReceiver + implements TurnGameController, CardCodes, CardGameReceiver { // Documentation inherited. public void willEnterPlace (PlaceObject plobj) @@ -54,6 +56,10 @@ public abstract class CardGameController extends GameController CardGameDecoder.RECEIVER_CODE); } + // Documentation inherited. + public void turnDidChange (Name turnHolder) + {} + /** * Called when the server deals the client a new hand of cards. Default * implementation does nothing. @@ -72,4 +78,27 @@ public abstract class CardGameController extends GameController */ public void receivedCardsFromPlayer (int plidx, Card[] cards) {} + + /** + * Dispatched to the client when the server has forced it to send + * a set of cards to another player. Default implementation does + * nothing. + * + * @param plidx the index of the player to which the cards were sent + * @param cards the cards sent + */ + public void sentCardsToPlayer (int plidx, Card[] cards) + {} + + /** + * Dispatched to the client when a set of cards is transferred between + * two other players in the game. Default implementation does nothing. + * + * @param fromidx the index of the player sending the cards + * @param toidx the index of the player receiving the cards + * @param cards the number of cards transferred + */ + public void cardsTransferredBetweenPlayers (int fromidx, int toidx, + int cards) + {} } diff --git a/src/java/com/threerings/parlor/card/client/CardGameDecoder.java b/src/java/com/threerings/parlor/card/client/CardGameDecoder.java index 0645a4e3a..e750ee708 100644 --- a/src/java/com/threerings/parlor/card/client/CardGameDecoder.java +++ b/src/java/com/threerings/parlor/card/client/CardGameDecoder.java @@ -24,6 +24,14 @@ public class CardGameDecoder extends InvocationDecoder * notifications. */ public static final int RECEIVED_CARDS_FROM_PLAYER = 2; + /** The method id used to dispatch {@link CardGameReceiver#sentCardsToPlayer} + * notifications. */ + public static final int SENT_CARDS_TO_PLAYER = 3; + + /** The method id used to dispatch {@link CardGameReceiver#cardsTransferredBetweenPlayers} + * notifications. */ + public static final int CARDS_TRANSFERRED_BETWEEN_PLAYERS = 4; + /** * Creates a decoder that may be registered to dispatch invocation * service notifications to the specified receiver. @@ -55,6 +63,18 @@ public class CardGameDecoder extends InvocationDecoder ); return; + case SENT_CARDS_TO_PLAYER: + ((CardGameReceiver)receiver).sentCardsToPlayer( + ((Integer)args[0]).intValue(), (Card[])args[1] + ); + return; + + case CARDS_TRANSFERRED_BETWEEN_PLAYERS: + ((CardGameReceiver)receiver).cardsTransferredBetweenPlayers( + ((Integer)args[0]).intValue(), ((Integer)args[1]).intValue(), ((Integer)args[2]).intValue() + ); + return; + default: super.dispatchNotification(methodId, args); } diff --git a/src/java/com/threerings/parlor/card/client/CardGameReceiver.java b/src/java/com/threerings/parlor/card/client/CardGameReceiver.java index 9be934aa6..cfcc83262 100644 --- a/src/java/com/threerings/parlor/card/client/CardGameReceiver.java +++ b/src/java/com/threerings/parlor/card/client/CardGameReceiver.java @@ -47,4 +47,24 @@ public interface CardGameReceiver extends InvocationReceiver * @param cards the cards received */ public void receivedCardsFromPlayer (int plidx, Card[] cards); + + /** + * Dispatched to the client when the server has forced it to send + * a set of cards to another player. + * + * @param plidx the index of the player to which the cards were sent + * @param cards the cards sent + */ + public void sentCardsToPlayer (int plidx, Card[] cards); + + /** + * Dispatched to the client when a set of cards is transferred between + * two other players in the game. + * + * @param fromidx the index of the player sending the cards + * @param toidx the index of the player receiving the cards + * @param cards the number of cards transferred + */ + public void cardsTransferredBetweenPlayers (int fromidx, int toidx, + int cards); } diff --git a/src/java/com/threerings/parlor/card/client/CardGameService.java b/src/java/com/threerings/parlor/card/client/CardGameService.java deleted file mode 100644 index 1aa9ae3f8..000000000 --- a/src/java/com/threerings/parlor/card/client/CardGameService.java +++ /dev/null @@ -1,26 +0,0 @@ -// -// $Id: RoisterService.java 17829 2004-11-12 20:24:43Z mdb $ - -package com.threerings.parlor.card.client; - -import com.threerings.parlor.card.data.Card; - -import com.threerings.presents.client.Client; -import com.threerings.presents.client.InvocationService; - -/** - * Service calls related to card games. - */ -public interface CardGameService extends InvocationService -{ - /** - * Sends a set of cards to another player. - * - * @param client the client object - * @param playerIndex the index of the player to receive the cards - * @param cards the cards to send - * @param cl a listener to notify on success/failure - */ - public void sendCardsToPlayer (Client client, int playerIndex, Card[] cards, - ConfirmListener cl); -} diff --git a/src/java/com/threerings/parlor/card/client/CardPanel.java b/src/java/com/threerings/parlor/card/client/CardPanel.java index bef661b05..648a1263c 100644 --- a/src/java/com/threerings/parlor/card/client/CardPanel.java +++ b/src/java/com/threerings/parlor/card/client/CardPanel.java @@ -21,6 +21,7 @@ package com.threerings.parlor.card.client; +import java.awt.Point; import java.awt.event.MouseEvent; import java.util.ArrayList; @@ -29,16 +30,19 @@ import java.util.Iterator; import javax.swing.event.MouseInputAdapter; import com.samskivert.util.ObserverList; +import com.samskivert.util.QuickSort; import com.threerings.media.FrameManager; import com.threerings.media.VirtualMediaPanel; - import com.threerings.media.image.Mirage; - +import com.threerings.media.sprite.PathAdapter; import com.threerings.media.sprite.Sprite; +import com.threerings.media.util.LinePath; +import com.threerings.media.util.Path; +import com.threerings.media.util.PathSequence; +import com.threerings.media.util.Pathable; import com.threerings.parlor.card.Log; - import com.threerings.parlor.card.data.Card; import com.threerings.parlor.card.data.CardCodes; import com.threerings.parlor.card.data.Deck; @@ -49,8 +53,947 @@ import com.threerings.parlor.card.data.Hand; * and manipulating playing cards. */ public abstract class CardPanel extends VirtualMediaPanel - implements CardCodes + implements CardCodes { + /** The selection mode in which cards are not selectable. */ + public static final int NONE = 0; + + /** The selection mode in which the user can play a single card. */ + public static final int PLAY_SINGLE = 1; + + /** The selection mode in which the user can select a single card. */ + public static final int SELECT_SINGLE = 2; + + /** The selection mode in which the user can select multiple cards. */ + public static final int SELECT_MULTIPLE = 3; + + /** + * A predicate class for {@link CardSprite}s. Provides control over which + * cards are selectable, playable, etc. + */ + public static interface CardSpritePredicate + { + /** + * Evaluates the specified sprite. + */ + public boolean evaluate (CardSprite sprite); + } + + /** + * A listener for card selection/deselection. + */ + public static interface CardSelectionObserver + { + /** + * Called when a card has been played. + */ + public void cardSpritePlayed (CardSprite sprite); + + /** + * Called when a card has been selected. + */ + public void cardSpriteSelected (CardSprite sprite); + + /** + * Called when a card has been deselected. + */ + public void cardSpriteDeselected (CardSprite sprite); + } + + /** + * Constructor. + * + * @param frameManager the frame manager + */ + public CardPanel (FrameManager frameManager) + { + super(frameManager); + + // add a listener for mouse events + CardListener cl = new CardListener(); + addMouseListener(cl); + addMouseMotionListener(cl); + } + + /** + * Returns the full-sized image for the back of a playing card. + */ + public abstract Mirage getCardBackImage (); + + /** + * Returns the full-sized image for the front of the specified card. + */ + public abstract Mirage getCardImage (Card card); + + /** + * Returns the small-sized image for the back of a playing card. + */ + public abstract Mirage getMicroCardBackImage (); + + /** + * Returns the small-sized image for the front of the specified card. + */ + public abstract Mirage getMicroCardImage (Card card); + + /** + * Sets the location of the hand (the location of the center of the hand's + * upper edge). + */ + public void setHandLocation (int x, int y) + { + _handLocation.setLocation(x, y); + } + + /** + * Sets the horizontal spacing between cards in the hand. + */ + public void setHandSpacing (int spacing) + { + _handSpacing = spacing; + } + + /** + * Sets the vertical distance to offset cards that are selectable or + * playable. + */ + public void setSelectableCardOffset (int offset) + { + _selectableCardOffset = offset; + } + + /** + * Sets the vertical distance to offset cards that are selected. + */ + public void setSelectedCardOffset (int offset) + { + _selectedCardOffset = offset; + } + + /** + * Sets the selection mode for the hand (NONE, PLAY_SINGLE, SELECT_SINGLE, + * or SELECT_MULTIPLE). Changing the selection mode can change the + * current selection (clearing the selection, for example, if disabling + * selection mode). + */ + public void setHandSelectionMode (int mode) + { + if (_handSelectionMode == mode) { + return; + } + _handSelectionMode = mode; + + // if entered non-selection or single-selection mode, deselect all + // or all but one card + if (mode != SELECT_MULTIPLE) { + CardSprite[] sprites = getSelectedHandSprites(); + int begin = (mode == SELECT_SINGLE ? 1 : 0); + for (int i = begin; i < sprites.length; i++) { + deselectHandSprite(sprites[i]); + } + } + + // update the offsets of all cards in the hand + updateHandOffsets(); + } + + /** + * Sets the selection predicate that determines which cards from the hand + * may be selected (if null, all cards may be selected). Changing the + * predicate does not change the current selection. + */ + public void setHandSelectionPredicate (CardSpritePredicate pred) + { + _handSelectionPredicate = pred; + + // update the offsets of all cards in the hand + updateHandOffsets(); + } + + /** + * Returns the currently selected hand sprite (null if no sprites are + * selected, the first sprite if multiple sprites are selected). + */ + public CardSprite getSelectedHandSprite () + { + return _selectedHandSprites.size() == 0 ? + null : (CardSprite)_selectedHandSprites.get(0); + } + + /** + * Returns an array containing the currently selected hand sprites + * (returns an empty array if no sprites are selected). + */ + public CardSprite[] getSelectedHandSprites () + { + return (CardSprite[])_selectedHandSprites.toArray( + new CardSprite[_selectedHandSprites.size()]); + } + + /** + * Programmatically plays a sprite in the hand. + */ + public void playHandSprite (final CardSprite sprite) + { + // notify the observers + ObserverList.ObserverOp op = new ObserverList.ObserverOp() { + public boolean apply (Object obs) { + ((CardSelectionObserver)obs).cardSpritePlayed(sprite); + return true; + } + }; + _handSelectionObservers.apply(op); + } + + /** + * Programmatically selects a sprite in the hand. + */ + public void selectHandSprite (final CardSprite sprite) + { + // make sure it's not already selected + if (_selectedHandSprites.contains(sprite)) { + return; + } + + // if in single card mode and there's another card selected, + // deselect it + if (_handSelectionMode == SELECT_SINGLE) { + CardSprite oldSprite = getSelectedHandSprite(); + if (oldSprite != null) { + deselectHandSprite(oldSprite); + } + } + + // add to list and update offset + _selectedHandSprites.add(sprite); + sprite.setLocation(sprite.getX(), + _handLocation.y - _selectedCardOffset); + + // notify the observers + ObserverList.ObserverOp op = new ObserverList.ObserverOp() { + public boolean apply (Object obs) { + ((CardSelectionObserver)obs).cardSpriteSelected(sprite); + return true; + } + }; + _handSelectionObservers.apply(op); + } + + /** + * Programmatically deselects a sprite in the hand. + */ + public void deselectHandSprite (final CardSprite sprite) + { + // make sure it's selected + if (!_selectedHandSprites.contains(sprite)) { + return; + } + + // remove from list and update offset + _selectedHandSprites.remove(sprite); + sprite.setLocation(sprite.getX(), _handLocation.y); + + // notify the observers + ObserverList.ObserverOp op = new ObserverList.ObserverOp() { + public boolean apply (Object obs) { + ((CardSelectionObserver)obs).cardSpriteDeselected(sprite); + return true; + } + }; + _handSelectionObservers.apply(op); + } + + /** + * Clears any existing hand sprite selection. + */ + public void clearHandSelection () + { + CardSprite[] sprites = getSelectedHandSprites(); + for (int i = 0; i < sprites.length; i++) { + deselectHandSprite(sprites[i]); + } + } + + /** + * Adds an object to the list of observers to notify when cards in the + * hand are selected/deselected. + */ + public void addHandSelectionObserver (CardSelectionObserver obs) + { + _handSelectionObservers.add(obs); + } + + /** + * Removes an object from the hand selection observer list. + */ + public void removeHandSelectionObserver (CardSelectionObserver obs) + { + _handSelectionObservers.remove(obs); + } + + /** + * Fades a hand of cards in. + * + * @param hand the hand of cards + * @param fadeDuration the amount of time to spend fading in + * the entire hand + */ + public void setHand (Hand hand, long fadeDuration) + { + // make sure no cards are hanging around + clearHand(); + + // create the sprites + int size = hand.size(); + for (int i = 0; i < size; i++) { + CardSprite cs = new CardSprite(this, (Card)hand.get(i)); + _handSprites.add(cs); + } + + // sort them + QuickSort.sort(_handSprites); + + // fade them in at proper locations and layers + long cardDuration = fadeDuration / size; + for (int i = 0; i < size; i++) { + CardSprite cs = (CardSprite)_handSprites.get(i); + cs.setLocation(getHandX(size, i), _handLocation.y); + cs.setRenderOrder(i); + cs.addSpriteObserver(_handSpriteObserver); + addSprite(cs); + cs.fadeIn(i * cardDuration, cardDuration); + } + } + + /** + * Fades a hand of cards in face-down. + * + * @param size the size of the hand + * @param fadeDuration the amount of time to spend fading in + * each card + */ + public void setHand (int size, long fadeDuration) + { + // fill hand will null entries to signify unknown cards + Hand hand = new Hand(); + for (int i = 0; i < size; i++) { + hand.add(null); + } + setHand(hand, fadeDuration); + } + + /** + * Shows a hand that was previous set face-down. + * + * @param hand the hand of cards + */ + public void showHand (Hand hand) + { + // sort the hand + QuickSort.sort(hand); + + // set the sprites + int len = Math.min(_handSprites.size(), hand.size()); + for (int i = 0; i < len; i++) { + CardSprite cs = (CardSprite)_handSprites.get(i); + cs.setCard((Card)hand.get(i)); + } + } + + /** + * Returns the first sprite in the hand that corresponds to the + * specified card, or null if the card is not in the hand. + */ + public CardSprite getHandSprite (Card card) + { + return getCardSprite(_handSprites, card); + } + + /** + * Clears all cards from the hand. + */ + public void clearHand () + { + clearHandSelection(); + clearSprites(_handSprites); + } + + /** + * Clears all cards from the board. + */ + public void clearBoard () + { + clearSprites(_boardSprites); + } + + /** + * Flies a set of cards from the hand into the ether. + * + * @param cards the card sprites to remove from the hand + * @param dest the point to fly the cards to + * @param flightDuration the duration of the cards' flight + * @param fadePortion the amount of time to spend fading out + * as a proportion of the flight duration + */ + public void flyFromHand (CardSprite[] cards, Point dest, + long flightDuration, float fadePortion) + { + // fly each sprite over, removing it from the hand immediately and + // from the board when it finishes its path + for (int i = 0; i < cards.length; i++) { + LinePath flight = new LinePath(dest, flightDuration); + cards[i].addSpriteObserver(_pathEndRemover); + cards[i].moveAndFadeOut(flight, flightDuration, fadePortion); + removeFromHand(cards[i]); + } + + // adjust the hand to cover the hole + adjustHand(flightDuration, false); + } + + /** + * Flies a set of cards from the ether into the hand. + * + * @param cards the cards to add to the hand + * @param src the point to fly the cards from + * @param flightDuration the duration of the cards' flight + * @param fadePortion the amount of time to spend fading in + * as a proportion of the flight duration + */ + public void flyIntoHand (Card[] cards, Point src, long flightDuration, + float fadePortion) + { + // first create the sprites and add them to the list + CardSprite[] sprites = new CardSprite[cards.length]; + for (int i = 0; i < cards.length; i++) { + sprites[i] = new CardSprite(this, cards[i]); + _handSprites.add(sprites[i]); + } + + // settle the hand + adjustHand(flightDuration, true); + + // then set the layers and fly the cards in + int size = _handSprites.size(); + for (int i = 0; i < sprites.length; i++) { + int idx = _handSprites.indexOf(sprites[i]); + sprites[i].setLocation(src.x, src.y); + sprites[i].setRenderOrder(idx); + sprites[i].addSpriteObserver(_handSpriteObserver); + addSprite(sprites[i]); + LinePath flight = new LinePath(new Point(getHandX(size, idx), + _handLocation.y), flightDuration); + sprites[i].moveAndFadeIn(flight, flightDuration, fadePortion); + } + } + + /** + * Flies a set of cards from the ether into the ether. + * + * @param cards the cards to fly across + * @param src the point to fly the cards from + * @param dest the point to fly the cards to + * @param flightDuration the duration of the cards' flight + * @param cardDelay the amount of time to wait between cards + * @param fadePortion the amount of time to spend fading in and out + * as a proportion of the flight duration + */ + public void flyAcross (Card[] cards, Point src, Point dest, + long flightDuration, long cardDelay, float fadePortion) + { + for (int i = 0; i < cards.length; i++) { + // add on top of all board sprites + CardSprite cs = new CardSprite(this, cards[i]); + cs.setRenderOrder(getHighestBoardLayer() + 1 + i); + cs.setLocation(src.x, src.y); + addSprite(cs); + + // prepend an initial delay to all cards after the first + Path path; + long pathDuration; + LinePath flight = new LinePath(dest, flightDuration); + if (i > 0) { + long delayDuration = cardDelay * i; + LinePath delay = new LinePath(src, delayDuration); + path = new PathSequence(delay, flight); + pathDuration = delayDuration + flightDuration; + + } else { + path = flight; + pathDuration = flightDuration; + } + cs.addSpriteObserver(_pathEndRemover); + cs.moveAndFadeInAndOut(path, pathDuration, fadePortion); + } + } + + /** + * Flies a set of cards from the ether into the ether face-down. + * + * @param number the number of cards to fly across + * @param src the point to fly the cards from + * @param dest the point to fly the cards to + * @param flightDuration the duration of the cards' flight + * @param cardDelay the amount of time to wait between cards + * @param fadePortion the amount of time to spend fading in and out + * as a proportion of the flight duration + */ + public void flyAcross (int number, Point src, Point dest, + long flightDuration, long cardDelay, float fadePortion) + { + // use null values to signify unknown cards + flyAcross(new Card[number], src, dest, flightDuration, + cardDelay, fadePortion); + } + + /** + * Flies a card from the hand onto the board. + * + * @param card the sprite to remove from the hand + * @param dest the point to fly the card to + * @param flightDuration the duration of the card's flight + */ + public void flyFromHandToBoard (CardSprite card, Point dest, + long flightDuration) + { + // fly it over + LinePath flight = new LinePath(dest, flightDuration); + card.move(flight); + + // lower the board so that the card from hand is on top + lowerBoardSprites(card.getRenderOrder() - 1); + + // move from one list to the other + removeFromHand(card); + _boardSprites.add(card); + + // adjust the hand to cover the hole + adjustHand(flightDuration, false); + } + + /** + * Flies a card from the ether onto the board. + * + * @param card the card to add to the board + * @param src the point to fly the card from + * @param dest the point to fly the card to + * @param flightDuration the duration of the card's flight + * @param fadePortion the amount of time to spend fading in as a + * proportion of the flight duration + */ + public void flyToBoard (Card card, Point src, Point dest, + long flightDuration, float fadePortion) + { + // add it on top of the existing cards + CardSprite cs = new CardSprite(this, card); + cs.setRenderOrder(getHighestBoardLayer() + 1); + cs.setLocation(src.x, src.y); + addSprite(cs); + _boardSprites.add(cs); + + // and fly it over + LinePath flight = new LinePath(dest, flightDuration); + cs.moveAndFadeIn(flight, flightDuration, fadePortion); + } + + /** + * Adds a card to the board immediately. + * + * @param card the card to add to the board + * @param dest the point at which to add the card + */ + public void addToBoard (Card card, Point dest) + { + CardSprite cs = new CardSprite(this, card); + cs.setRenderOrder(getHighestBoardLayer() + 1); + cs.setLocation(dest.x, dest.y); + addSprite(cs); + _boardSprites.add(cs); + } + + /** + * Flies a set of cards from the board into the ether. + * + * @param cards the cards to remove from the board + * @param dest the point to fly the cards to + * @param flightDuration the duration of the cards' flight + * @param fadePortion the amount of time to spend fading out as a + * proportion of the flight duration + */ + public void flyFromBoard (CardSprite[] cards, Point dest, + long flightDuration, float fadePortion) + { + for (int i = 0; i < cards.length; i++) { + LinePath flight = new LinePath(dest, flightDuration); + cards[i].addSpriteObserver(_pathEndRemover); + cards[i].moveAndFadeOut(flight, flightDuration, fadePortion); + _boardSprites.remove(cards[i]); + } + } + + /** + * Flies a set of cards from the board into the ether through an + * intermediate point. + * + * @param cards the cards to remove from the board + * @param dest1 the first point to fly the cards to + * @param dest2 the final destination of the cards + * @param flightDuration the duration of the cards' flight + * @param fadePortion the amount of time to spend fading out as a + * proportion of the flight duration + */ + public void flyFromBoard (CardSprite[] cards, Point dest1, Point dest2, + long flightDuration, float fadePortion) + { + for (int i = 0; i < cards.length; i++) { + PathSequence flight = new PathSequence( + new LinePath(dest1, flightDuration/2), + new LinePath(dest1, dest2, flightDuration/2)); + cards[i].addSpriteObserver(_pathEndRemover); + cards[i].moveAndFadeOut(flight, flightDuration, fadePortion); + _boardSprites.remove(cards[i]); + } + } + + /** + * Returns the first card sprite in the specified list that represents + * the specified card, or null if there is no such sprite in the list. + */ + protected CardSprite getCardSprite (ArrayList list, Card card) + { + for (int i = 0; i < list.size(); i++) { + CardSprite cs = (CardSprite)list.get(i); + if (card.equals(cs.getCard())) { + return cs; + } + } + return null; + } + + /** + * Expands or collapses the hand to accommodate new cards or cover the + * space left by removed cards. Skips unmanaged sprites. + * + * @param adjustDuration the amount of time to spend settling the cards + * into their new locations + * @param updateLayers whether or not to update the layers of the cards + */ + protected void adjustHand (long adjustDuration, boolean updateLayers) + { + // Sort the hand + QuickSort.sort(_handSprites); + + // Move each card to its proper position (and, optionally, layer) + int size = _handSprites.size(); + for (int i = 0; i < size; i++) { + CardSprite cs = (CardSprite)_handSprites.get(i); + if (!isManaged(cs)) { + continue; + } + if (updateLayers) { + removeSprite(cs); + cs.setRenderOrder(i); + addSprite(cs); + } + LinePath adjust = new LinePath(new Point(getHandX(size, i), + getHandY(cs, false)), adjustDuration); + cs.move(adjust); + } + } + + /** + * Removes a card from the hand, deselecting it if selected. + */ + protected void removeFromHand (CardSprite card) + { + if (_selectedHandSprites.contains(card)) { + deselectHandSprite(card); + } + _handSprites.remove(card); + } + + /** + * Updates the offsets of all the cards in the hand. If there is only + * one selectable card, that card will always be raised slightly. + */ + protected void updateHandOffsets () + { + int size = _handSprites.size(); + for (int i = 0; i < size; i++) { + CardSprite cs = (CardSprite)_handSprites.get(i); + cs.setLocation(cs.getX(), getHandY(cs, false)); + } + } + + /** + * Given the location and spacing of the hand, returns the x location of + * the card at the specified index within a hand of the specified size. + */ + protected int getHandX (int size, int idx) + { + // get the card width from the image if not yet known + if (_cardWidth == 0) { + _cardWidth = getCardBackImage().getWidth(); + } + // first compute the width of the entire hand, then use that to + // determine the centered location + int width = (size - 1) * _handSpacing + _cardWidth; + return (_handLocation.x - width/2) + idx * _handSpacing; + } + + /** + * Determines the y location of the specified card, given its selection + * state. + * + * @param mouseOver whether or not the mouse cursor is currently on the + * card + */ + protected int getHandY (CardSprite card, boolean mouseOver) + { + if (_selectedHandSprites.contains(card)) { + return _handLocation.y - _selectedCardOffset; + + } else if (isSelectable(card) && (mouseOver || + getSelectableCount() == 1)) { + return _handLocation.y - _selectableCardOffset; + + } else { + return _handLocation.y; + } + } + + /** + * Returns the number of sprites in the hand that are selectable. + */ + protected int getSelectableCount () + { + int size = _handSprites.size(), count = 0; + for (int i = 0; i < size; i++) { + if (isSelectable((CardSprite)_handSprites.get(i))) { + count++; + } + } + return count; + } + + /** + * Given the current selection mode and predicate, determines if the + * specified sprite is selectable. + */ + protected boolean isSelectable (CardSprite sprite) { + return _handSelectionMode != NONE && + (_handSelectionPredicate == null || + _handSelectionPredicate.evaluate(sprite)); + } + + /** + * Lowers all board sprites so that they are rendered at or below the + * specified layer. + */ + protected void lowerBoardSprites (int layer) + { + // see if they're already lower + int highest = getHighestBoardLayer(); + if (highest <= layer) { + return; + } + + // lower them just enough + int size = _boardSprites.size(), adjustment = layer - highest; + for (int i = 0; i < size; i++) { + CardSprite cs = (CardSprite)_boardSprites.get(i); + removeSprite(cs); + cs.setRenderOrder(cs.getRenderOrder() + adjustment); + addSprite(cs); + } + } + + /** + * Returns the highest render order of any sprite on the board. + */ + protected int getHighestBoardLayer () + { + // must be at least zero, because that's the lowest number we can push + // the sprites down to (the layer of the first card in the hand) + int size = _boardSprites.size(), highest = 0; + for (int i = 0; i < size; i++) { + highest = Math.max(highest, + ((CardSprite)_boardSprites.get(i)).getRenderOrder()); + } + return highest; + } + + /** + * Clears an array of sprites from the specified list and from the panel. + */ + protected void clearSprites (ArrayList sprites) + { + for (Iterator it = sprites.iterator(); it.hasNext(); ) { + removeSprite((CardSprite)it.next()); + it.remove(); + } + } + + /** The width of the playing cards. */ + protected int _cardWidth; + + /** The sprites for cards within the hand. */ + protected ArrayList _handSprites = new ArrayList(); + + /** The sprites for cards within the hand that have been selected. */ + protected ArrayList _selectedHandSprites = new ArrayList(); + + /** The sprites for cards within the hand that are selectable. */ + protected ArrayList _selectableHandSprites = new ArrayList(); + + /** The current selection mode for the hand. */ + protected int _handSelectionMode; + + /** The predicate that determines which cards are selectable (if null, all + * cards are selectable). */ + protected CardSpritePredicate _handSelectionPredicate; + + /** Observers of hand card selection/deselection. */ + protected ObserverList _handSelectionObservers = new ObserverList( + ObserverList.FAST_UNSAFE_NOTIFY); + + /** The location of the center of the hand's upper edge. */ + protected Point _handLocation = new Point(); + + /** The horizontal distance between cards in the hand. */ + protected int _handSpacing; + + /** The vertical distance to offset cards that are selectable. */ + protected int _selectableCardOffset; + + /** The vertical distance to offset cards that are selected. */ + protected int _selectedCardOffset; + + /** The sprites for cards on the board. */ + protected ArrayList _boardSprites = new ArrayList(); + + /** A path observer that removes the sprite at the end of its path. */ + protected PathAdapter _pathEndRemover = new PathAdapter() { + public void pathCompleted (Sprite sprite, Path path, long when) { + removeSprite(sprite); + } + }; + + /** Listens for interactions with cards in hand. */ + protected CardSpriteObserver _handSpriteObserver = + new CardSpriteObserver() { + public void cardSpriteClicked (CardSprite sprite, MouseEvent me) { + // select, deselect, or play card in hand + if (_selectedHandSprites.contains(sprite)) { + deselectHandSprite(sprite); + + } else if (_handSprites.contains(sprite) && isSelectable(sprite)) { + if (_handSelectionMode == PLAY_SINGLE) { + playHandSprite(sprite); + + } else { + selectHandSprite(sprite); + } + } + } + + public void cardSpriteEntered (CardSprite sprite, MouseEvent me) { + // update the offset + if (_handSprites.contains(sprite)) { + sprite.setLocation(sprite.getX(), getHandY(sprite, true)); + } + } + + public void cardSpriteExited (CardSprite sprite, MouseEvent me) { + // update the offset + if (_handSprites.contains(sprite)) { + sprite.setLocation(sprite.getX(), getHandY(sprite, false)); + } + } + + public void cardSpriteDragged (CardSprite sprite, MouseEvent me) { + } + }; + + /** Listens for mouse interactions with cards. */ + protected class CardListener extends MouseInputAdapter + { + public void mousePressed (MouseEvent me) + { + if (_activeCardSprite != null && + isManaged(_activeCardSprite)) { + _handleX = _activeCardSprite.getX() - me.getX(); + _handleY = _activeCardSprite.getY() - me.getY(); + _hasBeenDragged = false; + } + } + + public void mouseReleased (MouseEvent me) + { + if (_activeCardSprite != null && + isManaged(_activeCardSprite) && + _hasBeenDragged) { + _activeCardSprite.queueNotification( + new CardSpriteDraggedOp(_activeCardSprite, me) + ); + } + } + + public void mouseClicked (MouseEvent me) + { + if (_activeCardSprite != null && + isManaged(_activeCardSprite)) { + _activeCardSprite.queueNotification( + new CardSpriteClickedOp(_activeCardSprite, me) + ); + } + } + + public void mouseMoved (MouseEvent me) + { + Sprite newHighestHit = _spritemgr.getHighestHitSprite( + me.getX(), me.getY()); + + CardSprite newActiveCardSprite = + (newHighestHit instanceof CardSprite ? + (CardSprite)newHighestHit : null); + + if (_activeCardSprite != newActiveCardSprite) { + if (_activeCardSprite != null && + isManaged(_activeCardSprite)) { + _activeCardSprite.queueNotification( + new CardSpriteExitedOp(_activeCardSprite, me) + ); + } + _activeCardSprite = newActiveCardSprite; + if (_activeCardSprite != null) { + _activeCardSprite.queueNotification( + new CardSpriteEnteredOp(_activeCardSprite, me) + ); + } + } + } + + public void mouseDragged (MouseEvent me) + { + if (_activeCardSprite != null && + isManaged(_activeCardSprite) && + _activeCardSprite.isDraggable()) { + _activeCardSprite.setLocation( + me.getX() + _handleX, + me.getY() + _handleY + ); + _hasBeenDragged = true; + + } else { + mouseMoved(me); + } + } + + protected CardSprite _activeCardSprite; + protected int _handleX, _handleY; + protected boolean _hasBeenDragged; + } + /** Calls CardSpriteObserver.cardSpriteClicked. */ protected static class CardSpriteClickedOp implements ObserverList.ObserverOp @@ -63,8 +1006,10 @@ public abstract class CardPanel extends VirtualMediaPanel public boolean apply (Object observer) { - ((CardSpriteObserver)observer).cardSpriteClicked(_sprite, - _me); + if (observer instanceof CardSpriteObserver) { + ((CardSpriteObserver)observer).cardSpriteClicked(_sprite, + _me); + } return true; } @@ -84,7 +1029,10 @@ public abstract class CardPanel extends VirtualMediaPanel public boolean apply (Object observer) { - ((CardSpriteObserver)observer).cardSpriteEntered(_sprite, _me); + if (observer instanceof CardSpriteObserver) { + ((CardSpriteObserver)observer).cardSpriteEntered(_sprite, + _me); + } return true; } @@ -104,7 +1052,9 @@ public abstract class CardPanel extends VirtualMediaPanel public boolean apply (Object observer) { - ((CardSpriteObserver)observer).cardSpriteExited(_sprite, _me); + if (observer instanceof CardSpriteObserver) { + ((CardSpriteObserver)observer).cardSpriteExited(_sprite, _me); + } return true; } @@ -124,114 +1074,14 @@ public abstract class CardPanel extends VirtualMediaPanel public boolean apply (Object observer) { - ((CardSpriteObserver)observer).cardSpriteDragged(_sprite, _me); + if (observer instanceof CardSpriteObserver) { + ((CardSpriteObserver)observer).cardSpriteDragged(_sprite, + _me); + } return true; } protected CardSprite _sprite; protected MouseEvent _me; } - - /** - * Constructor. - * - * @param frameManager the frame manager - */ - public CardPanel (FrameManager frameManager) - { - super(frameManager); - - MouseInputAdapter mia = new MouseInputAdapter() { - public void mousePressed (MouseEvent me) { - if (_activeCardSprite != null && - isManaged(_activeCardSprite)) { - _handleX = _activeCardSprite.getX() - me.getX(); - _handleY = _activeCardSprite.getY() - me.getY(); - _hasBeenDragged = false; - } - } - public void mouseReleased (MouseEvent me) { - if (_activeCardSprite != null && - isManaged(_activeCardSprite) && - _hasBeenDragged) { - _activeCardSprite.queueNotification( - new CardSpriteDraggedOp(_activeCardSprite, me) - ); - } - } - public void mouseClicked (MouseEvent me) { - if (_activeCardSprite != null && - isManaged(_activeCardSprite)) { - _activeCardSprite.queueNotification( - new CardSpriteClickedOp(_activeCardSprite, me) - ); - } - } - public void mouseMoved (MouseEvent me) - { - Sprite newHighestHit = _spritemgr.getHighestHitSprite( - me.getX(), me.getY()); - - CardSprite newActiveCardSprite = - (newHighestHit instanceof CardSprite ? - (CardSprite)newHighestHit : null); - - if (_activeCardSprite != newActiveCardSprite) { - if (_activeCardSprite != null && - isManaged(_activeCardSprite)) { - _activeCardSprite.queueNotification( - new CardSpriteExitedOp(_activeCardSprite, me) - ); - } - _activeCardSprite = newActiveCardSprite; - if (_activeCardSprite != null) { - _activeCardSprite.queueNotification( - new CardSpriteEnteredOp(_activeCardSprite, me) - ); - } - } - } - public void mouseDragged (MouseEvent me) - { - if (_activeCardSprite != null && - isManaged(_activeCardSprite) && - _activeCardSprite.isDraggable()) { - _activeCardSprite.setLocation( - me.getX() + _handleX, - me.getY() + _handleY - ); - _hasBeenDragged = true; - } else { - mouseMoved(me); - } - } - }; - - addMouseListener(mia); - addMouseMotionListener(mia); - } - - /** - * Returns the image for the back of a playing card. - * - * @return the card back image - */ - public abstract Mirage getCardBackImage (); - - /** - * Returns the image for the front of the specified card. - * - * @param card the desired card - * @return the card front image - */ - public abstract Mirage getCardImage (Card card); - - /** The active card sprite. */ - protected CardSprite _activeCardSprite; - - /** The location of the cursor in the active sprite. */ - protected int _handleX, _handleY; - - /** Whether or not the active sprite has been dragged. */ - protected boolean _hasBeenDragged; } diff --git a/src/java/com/threerings/parlor/card/client/CardSprite.java b/src/java/com/threerings/parlor/card/client/CardSprite.java index fcb8864c4..8ff0a7a0b 100644 --- a/src/java/com/threerings/parlor/card/client/CardSprite.java +++ b/src/java/com/threerings/parlor/card/client/CardSprite.java @@ -21,9 +21,13 @@ package com.threerings.parlor.card.client; -import com.threerings.media.image.Mirage; +import java.awt.AlphaComposite; +import java.awt.Composite; +import java.awt.Graphics2D; +import com.threerings.media.image.Mirage; import com.threerings.media.sprite.OrientableImageSprite; +import com.threerings.media.util.Path; import com.threerings.parlor.card.data.Card; @@ -31,12 +35,14 @@ import com.threerings.parlor.card.data.Card; * A sprite representing a playing card. */ public class CardSprite extends OrientableImageSprite + implements Comparable { /** * Creates a new upward-facing card sprite. * * @param panel the panel responsible for the sprite - * @param card the card to depict + * @param card the card to depict (can be null, in which case the + * card back will be shown) */ public CardSprite (CardPanel panel, Card card) { @@ -127,13 +133,199 @@ public class CardSprite extends OrientableImageSprite return _draggable; } + /** + * Sets the alpha value of this sprite. + */ + public void setAlpha (float alpha) + { + if (alpha < 0.0f) { + alpha = 0.0f; + + } else if (alpha > 1.0f) { + alpha = 1.0f; + } + if (alpha != _alphaComposite.getAlpha()) { + _alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, + alpha); + if (_mgr != null) { + _mgr.getRegionManager().invalidateRegion(_bounds); + } + } + } + + /** + * Returns the alpha value of this sprite. + */ + public float getAlpha () + { + return _alphaComposite.getAlpha(); + } + + /** + * Fades this sprite in over the specified duration after + * waiting for the specified delay. + */ + public void fadeIn (long delay, long duration) + { + setAlpha(0.0f); + + _fadeStamp = 0; + _fadeDelay = delay; + _fadeInDuration = duration; + } + + /** + * Puts this sprite on the specified path and fades it in over + * the specified duration. + * + * @param path the path to move along + * @param fadePortion the portion of time to spend fading in, from 0.0f (no time) + * to 1.0f (the entire time) + */ + public void moveAndFadeIn (Path path, long pathDuration, float fadePortion) + { + move(path); + + setAlpha(0.0f); + + _fadeInDuration = (long)(pathDuration*fadePortion); + } + + /** + * Puts this sprite on the specified path and fades it out over + * the specified duration. + * + * @param path the path to move along + * @param pathDuration the duration of the path + * @param fadePortion the portion of time to spend fading out, from 0.0f (no time) + * to 1.0f (the entire time) + */ + public void moveAndFadeOut (Path path, long pathDuration, float fadePortion) + { + move(path); + + setAlpha(1.0f); + + _pathDuration = pathDuration; + _fadeOutDuration = (long)(pathDuration*fadePortion); + } + + /** + * Puts this sprite on the specified path, fading it in over the specified + * duration at the beginning and fading it out at the end. + * + * @param path the path to move along + * @param pathDuration the duration of the path + * @param fadePortion the portion of time to spend fading in/out, from + * 0.0f (no time) to 1.0f (the entire time) + */ + public void moveAndFadeInAndOut (Path path, long pathDuration, + float fadePortion) + { + move(path); + + setAlpha(0.0f); + + _pathDuration = pathDuration; + _fadeInDuration = _fadeOutDuration = (long)(pathDuration*fadePortion); + } + + // Documentation inherited. + public void tick (long tickStamp) + { + super.tick(tickStamp); + + if (_fadeInDuration != -1) { + if (_path != null && (tickStamp-_pathStamp) <= _fadeInDuration) { + // fading in while moving + float alpha = (float)(tickStamp-_pathStamp)/_fadeInDuration; + if (alpha >= 1.0f) { + // fade-in complete + setAlpha(1.0f); + _fadeInDuration = -1; + + } else { + setAlpha(alpha); + } + + } else { + // fading in while stationary + if (_fadeStamp == 0) { + // store the time at which fade started + _fadeStamp = tickStamp; + } + if (tickStamp > _fadeStamp + _fadeDelay) { + // initial delay has passed + float alpha = (float)(tickStamp-_fadeStamp-_fadeDelay)/_fadeInDuration; + if (alpha >= 1.0f) { + // fade-in complete + setAlpha(1.0f); + _fadeInDuration = -1; + + } else { + setAlpha(alpha); + } + } + } + + } else if(_fadeOutDuration != -1 && _pathStamp+_pathDuration-tickStamp + <= _fadeOutDuration) { + // fading out while moving + float alpha = (float)(_pathStamp+_pathDuration-tickStamp)/_fadeOutDuration; + setAlpha(alpha); + } + } + + // Documentation inherited. + public void pathCompleted (long timestamp) + { + super.pathCompleted(timestamp); + + if (_fadeInDuration != -1) { + setAlpha(1.0f); + _fadeInDuration = -1; + + } else if(_fadeOutDuration != -1) { + setAlpha(0.0f); + _fadeOutDuration = -1; + } + } + + // Documentation inherited. + public void paint (Graphics2D gfx) + { + if (_alphaComposite.getAlpha() < 1.0f) { + Composite ocomp = gfx.getComposite(); + gfx.setComposite(_alphaComposite); + super.paint(gfx); + gfx.setComposite(ocomp); + + } else { + super.paint(gfx); + } + } + + /** + * Compares this to another card sprite based on their cards. + */ + public int compareTo (Object other) + { + CardSprite cs = (CardSprite)other; + if (_card == null || cs._card == null) { + return 0; + + } else { + return _card.compareTo(cs._card); + } + } + /** * Updates the mirage according to the current state. */ protected void updateMirage () { - setMirage(_facingUp ? _panel.getCardImage(_card) : - _panel.getCardBackImage()); + setMirage((_card != null && _facingUp ) ? + _panel.getCardImage(_card) : _panel.getCardBackImage()); } /** The panel responsible for the sprite. */ @@ -147,4 +339,23 @@ public class CardSprite extends OrientableImageSprite /** Whether or not the user can drag the card around the board. */ protected boolean _draggable; + + /** The alpha composite. */ + protected AlphaComposite _alphaComposite = + AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f); + + /** If fading in, the fade-in duration (otherwise -1). */ + protected long _fadeInDuration = -1; + + /** If fading in without moving, the fade-in delay. */ + protected long _fadeDelay; + + /** The time at which fading started. */ + protected long _fadeStamp = -1; + + /** If fading out, the fade-out duration (otherwise -1). */ + protected long _fadeOutDuration = -1; + + /** If fading out, the path duration. */ + protected long _pathDuration; } diff --git a/src/java/com/threerings/parlor/card/data/CardGameObject.java b/src/java/com/threerings/parlor/card/data/CardGameObject.java index 6ef05010f..14cd594f6 100644 --- a/src/java/com/threerings/parlor/card/data/CardGameObject.java +++ b/src/java/com/threerings/parlor/card/data/CardGameObject.java @@ -28,29 +28,4 @@ import com.threerings.parlor.game.data.GameObject; */ public class CardGameObject extends GameObject { - // AUTO-GENERATED: FIELDS START - /** The field name of the cardGameService field. */ - public static final String CARD_GAME_SERVICE = "cardGameService"; - // AUTO-GENERATED: FIELDS END - - /** The card game service interface. */ - public CardGameMarshaller cardGameService; - - // AUTO-GENERATED: METHODS START - /** - * Requests that the cardGameService field be set to the - * specified value. The local value will be updated immediately and an - * event will be propagated through the system to notify all listeners - * that the attribute did change. Proxied copies of this object (on - * clients) will apply the value change when they received the - * attribute changed notification. - */ - public void setCardGameService (CardGameMarshaller value) - { - CardGameMarshaller ovalue = this.cardGameService; - requestAttributeChange( - CARD_GAME_SERVICE, value, ovalue); - this.cardGameService = value; - } - // AUTO-GENERATED: METHODS END } diff --git a/src/java/com/threerings/parlor/card/data/Deck.java b/src/java/com/threerings/parlor/card/data/Deck.java index 351533b66..9f8613780 100644 --- a/src/java/com/threerings/parlor/card/data/Deck.java +++ b/src/java/com/threerings/parlor/card/data/Deck.java @@ -23,28 +23,20 @@ package com.threerings.parlor.card.data; import java.util.Collections; -import com.threerings.io.Streamable; - import com.threerings.util.StreamableArrayList; /** * Instances of this class represent decks of cards. */ -public class Deck implements CardCodes, - Streamable +public class Deck extends StreamableArrayList + implements CardCodes { - /** The cards in the deck. */ - public StreamableArrayList cards; - - /** * Default constructor creates an unshuffled deck of cards without * jokers. */ public Deck () { - cards = new StreamableArrayList(); - reset(false); } @@ -56,8 +48,6 @@ public class Deck implements CardCodes, */ public Deck (boolean includeJokers) { - cards = new StreamableArrayList(); - reset(includeJokers); } @@ -70,17 +60,17 @@ public class Deck implements CardCodes, */ public void reset (boolean includeJokers) { - cards.clear(); + clear(); - for (int i=SPADES;i<=DIAMONDS;i++) { - for (int j=2;j<=ACE;j++) { - cards.add(new Card(j, i)); + for (int i = SPADES; i <= DIAMONDS; i++) { + for (int j = 2; j <= ACE; j++) { + add(new Card(j, i)); } } if (includeJokers) { - cards.add(new Card(RED_JOKER, 3)); - cards.add(new Card(BLACK_JOKER, 3)); + add(new Card(RED_JOKER, 3)); + add(new Card(BLACK_JOKER, 3)); } } @@ -89,7 +79,7 @@ public class Deck implements CardCodes, */ public void shuffle () { - Collections.shuffle(cards); + Collections.shuffle(this); } /** @@ -101,15 +91,15 @@ public class Deck implements CardCodes, */ public Hand dealHand (int size) { - if (cards.size() < size) { + if (size() < size) { return null; - } - else { + + } else { Hand hand = new Hand(); - int cardsLeft = cards.size(); - for (int i=0;i 0; + } + + /** + * Determines the number of cards that belong to the specified suit within + * the array given. + */ + public static int countSuitMembers (PlayerCard[] cards, int suit) + { + int count = 0; + for (int i = 0; i < cards.length; i++) { + if (cards[i].card.getSuit() == suit) { + count++; + } + } + return count; + } + + /** + * Checks whether the proposed card follows the suit lead. + */ + public static boolean followsSuit (PlayerCard[] cardsPlayed, Card card) + { + return cardsPlayed[0].card.getSuit() == card.getSuit(); + } + + /** + * Returns the highest card (according to the standard A,K,...,2 ordering) + * in the suit lead, with an optional trump suit. + * + * @param trumpSuit the trump suit, or -1 for none + */ + public static PlayerCard getHighestInLeadSuit (PlayerCard[] cardsPlayed, + int trumpSuit) + { + PlayerCard highest = cardsPlayed[0]; + for (int i = 1; i < cardsPlayed.length; i++) { + PlayerCard other = cardsPlayed[i]; + if ((other.card.getSuit() == highest.card.getSuit() && + other.card.compareTo(highest.card) > 0) || + (other.card.getSuit() == trumpSuit && + highest.card.getSuit() != trumpSuit)) { + highest = other; + } + } + return highest; + } + + /** The locations of the other players for each player index. */ + protected static final int[][] RELATIVE_LOCATIONS = { + {BOTTOM, TOP, RIGHT, LEFT}, {TOP, BOTTOM, LEFT, RIGHT}, + {LEFT, RIGHT, BOTTOM, TOP}, {RIGHT, LEFT, TOP, BOTTOM} }; +}