package javass.jass; import javass.jass.Card.Color; import javass.jass.Card.Rank; import javass.bits.Bits32; import static javass.Preconditions.*; /** * The representation of a trick of a jass game in a packed form that is as an int. * @author Charles BEAUVILLE * @author Celia HOUSSIAUX * */ public final class PackedTrick { private PackedTrick() {} /** * An integer representing an invalid packed {@link Trick} that is an empty {@link Trick}. */ public static final int INVALID = ~0; private static final int LAST_CARD_INDEX = 18; private static final int CARD_COLOR_INDEX = 4; private static final int CARD_COLOR_SIZE = 2; private static final int TRUMP_INDEX = 30; private static final int FIRST_PLAYER_INDEX = 28; private static final int TRICK_INDEX_POS = 24; private static final int TRICK_INDEX_SIZE = 4; private static final int FIRST_TRICK_INDEX = 0; private static final int LAST_TRICK_INDEX = 8; private static final int FIRST_PLAYER_SIZE = 2; private static final int TRUMP_SIZE = 2; private static final int CARD_SIZE = 6; private static final int CARD_NBR = 4; /** * Checks if the given {@link PackedTrick} is valid, that is that no card is not invalid while the previous cards are and that it represents a real {@link Trick}. * @param pkTrick an integer representing a packed {@link Trick}. * @return a boolean whether or not the {@link Trick} is valid. */ public static boolean isValid(int pkTrick) { int trickIndex = Bits32.extract(pkTrick, TRICK_INDEX_POS, TRICK_INDEX_SIZE); boolean isValid = true; for (int i = 0; i <= LAST_CARD_INDEX; i += CARD_SIZE) if(Bits32.extract(pkTrick, i, CARD_SIZE) == PackedCard.INVALID) for (int j = i; j <= LAST_CARD_INDEX; j += CARD_SIZE) isValid &= Bits32.extract(pkTrick, j, CARD_SIZE) == PackedCard.INVALID; return trickIndex < Jass.TRICKS_PER_TURN && isValid; } /** * Gives the first empty {@link Trick}, meaning with no cards, a given trump and a given first player. * @param trump the {@link Color} of the trump for the {@link Trick}. * @param firstPlayer the {@link PlayerId} of the first player of the {@link Trick}. * @return an integer representing the first empty {@link Trick}. */ public static int firstEmpty(Color trump, PlayerId firstPlayer) { return Bits32.pack(PackedCard.INVALID, CARD_SIZE, PackedCard.INVALID, CARD_SIZE, PackedCard.INVALID, CARD_SIZE, PackedCard.INVALID, CARD_SIZE, FIRST_TRICK_INDEX, TRICK_INDEX_SIZE, firstPlayer.ordinal(), FIRST_PLAYER_SIZE, trump.ordinal(), TRUMP_SIZE); } /** * Gives the next empty {@link Trick}, meaning with the winner of the last {@link Trick} as first player, no {@link Card}, the next index and the same trump. * @param pkTrick an integer representing the packed version of a {@link Trick}. * @return an integer representing a the packed version of the next empty {@link Trick}. */ public static int nextEmpty(int pkTrick) { assert isValid(pkTrick): "Invalid trick in nextEmpty function of pkTrick"; return isLast(pkTrick) ? INVALID : Bits32.pack(PackedCard.INVALID, CARD_SIZE, PackedCard.INVALID, CARD_SIZE, PackedCard.INVALID, CARD_SIZE, PackedCard.INVALID, CARD_SIZE, index(pkTrick) + 1, TRICK_INDEX_SIZE, winningPlayer(pkTrick).ordinal(), FIRST_PLAYER_SIZE, trump(pkTrick).ordinal(), TRUMP_SIZE); } /** * Checks if the trick is the last {@link Trick} of the turn meaning that it's the 9th {@link Trick}. * @param pkTrick an integer representing the packed version of a {@link Trick}. * @return a boolean true if the index of the {@link Trick} is equal to the LAST_TRICK_INDEX. */ public static boolean isLast(int pkTrick) { assert isValid(pkTrick) : "Invalid trick in isLast function of pkTrick"; return index(pkTrick) == LAST_TRICK_INDEX; } /** * Checks if the {@link Trick} contains no card meaning it hasn't started. * @param pkTrick an integer representing the packed version of a {@link Trick}. * @return a boolean true if no {@link Card} is in the {@link Trick}. */ public static boolean isEmpty(int pkTrick) { assert isValid(pkTrick): "Invalid trick in isEmpty function of pkTrick"; return Bits32.extract(pkTrick, 0, CARD_SIZE * CARD_NBR) == Bits32.mask(0, CARD_SIZE * CARD_NBR); } /** * Checks if the {@link Trick} is full meaning that all {@link Card}s have been played. * @param pkTrick an integer representing the packed version of a {@link Trick}. * @return a boolean true if all the cards have been played. */ public static boolean isFull(int pkTrick) { assert isValid(pkTrick): "Invalid trick in isFull function of pkTrick"; return size(pkTrick) == TRICK_INDEX_SIZE; } /** * Computes the size of the {@link Trick}, meaning the number of {@link Card}s that have been played. * @param pkTrick an integer representing the packed version of a {@link Trick}. * @return an integer the number of {@link Card} that have been played. */ public static int size(int pkTrick) { assert isValid(pkTrick): "Invalid trick in size function of pkTrick"; int count = 0; for(int i = 0 ; i < PlayerId.COUNT ; ++i) if(card(pkTrick, i) != PackedCard.INVALID) ++ count; return count; } /** * Gives the {@link PlayerId} that played at a given index in the given packed {@link Trick}. * @param pkTrick an integer representing the packed version of a {@link Trick}. * @param index an integer the index of the {@link Card} played by the wanted player. * @throws IndexOutOfBoundsException if the index is bigger than the PlayerId.COUNT or negative * @return the {@link PlayerId} that played the {@link Card} at the given index. */ public static PlayerId player(int pkTrick, int index) { assert isValid(pkTrick): "Invalid trick in player function of pkTrick"; int i = checkIndex(index, PlayerId.COUNT); return PlayerId.ALL.get((firstPlayerIndex(pkTrick) + i) % PlayerId.COUNT); } /** * A method to get the trump {@link Color} of the {@link Trick}. * @param pkTrick an integer representing a packed {@link Trick}. * @return the trump {@link Color} of the {@link Trick}. */ public static Color trump(int pkTrick) { assert isValid(pkTrick): "Invalid trick in trump function of pkTrick"; return Color.ALL.get(Bits32.extract(pkTrick, TRUMP_INDEX, TRUMP_SIZE)); } /** * A method to get the index of the {@link Trick}. * @param pkTrick an integer representing a packed {@link Trick}. * @return an int the index of the given packed trick. */ public static int index(int pkTrick) { assert isValid(pkTrick): "Invalid trick in index function of pkTrick"; return Bits32.extract(pkTrick, TRICK_INDEX_POS, TRICK_INDEX_SIZE); } /** * A method to get the packed version of the index's {@link Card} in the packedTrick. * @param pkTrick an integer representing a packed {@link Trick}. * @param index an int the index of the wanted card. * @return the packed version of the index's {@link Card}. */ public static int card(int pkTrick, int index) { assert isValid(pkTrick): "Invalid trick in card function of pkTrick"; assert (index >= 0 && index <= CARD_NBR): "Invalid index in card function of pkTrick"; return Bits32.extract(pkTrick, index * CARD_SIZE, CARD_SIZE); } /** * A method that gives the same {@link Trick} that the one given (supposed non empty) with the pkCard added. * @param pkTrick an integer representing a packed {@link Trick}. * @param pkCard an int the packed representation of a card. * @return pkTrick with pkCard that has been added. */ public static int withAddedCard(int pkTrick, int pkCard) { assert isValid(pkTrick): "Invalid trick in withAddedCard function of pkTrick"; int start = size(pkTrick) * CARD_SIZE; return pkTrick & ~Bits32.mask(start, CARD_SIZE) | pkCard << start; } /** * A method to get the initial {@link Color} of the {@link Trick} (the {@link Color} of the first {@link Card}. * @param pkTrick an integer representing a packed {@link Trick}. * @return the {@link Trick}'s base's {@link Color}. */ public static Color baseColor(int pkTrick) { assert isValid(pkTrick): "Invalid trick in baseColor function of pkTrick"; return Color.ALL.get(Bits32.extract(pkTrick, CARD_COLOR_INDEX, CARD_COLOR_SIZE)); } /** * A method to get the subset in a packed version of the {@link Card} of pkHand which can be played as the next card of the {@link Trick} pkTrick (supposed non empty). * @param pkTrick an integer representing a packed {@link Trick}. * @param pkHand a long representing the packed version of a card set. * @return a pkCardSet, subset of pkHand. */ public static long playableCards(int pkTrick, long pkHand) { assert isValid(pkTrick): "Invalid trick in playableCards function of pkTrick"; assert PackedCardSet.isValid(pkHand): "Invalid hand in playableCards function of pkTrick"; long trumpsAbove = PackedCardSet.EMPTY; long trumps = PackedCardSet.subsetOfColor(pkHand, trump(pkTrick)); long baseColorCards = PackedCardSet.subsetOfColor(pkHand, baseColor(pkTrick)); long allButTrumps = PackedCardSet.difference(pkHand, trumps); long jackSingleton = PackedCardSet .singleton(PackedCard .pack(trump(pkTrick), Rank.JACK)); boolean hasTrumpAbove = false; boolean isTrump = false; boolean hasNoBaseColor = PackedCardSet.isEmpty(PackedCardSet.intersection(baseColorCards, pkHand)); boolean hasNoTrump = PackedCardSet.isEmpty(PackedCardSet.intersection(trumps, pkHand)); int winningCard = card(pkTrick, winningCardIndex(pkTrick)); if(isEmpty(pkTrick)) return pkHand; //Computes the trump cards of the hand that are better than the current winning card and puts them in the packed card set trumpsAbove. for (int i = 0; i < PackedCardSet.size(trumps); i++) if(PackedCard.isBetter(trump(pkTrick), PackedCardSet.get(trumps,i), winningCard)) { trumpsAbove = PackedCardSet.add(trumpsAbove, PackedCardSet.get(trumps,i)); hasTrumpAbove = true; } //Checks if any player has played a trump color card and puts the result in the boolean isTrump. for (int j = 0; j < size(pkTrick); j++) if(PackedCard.color(card(pkTrick, j)).equals(trump(pkTrick))) isTrump = true; //Returns the entire hand if it contains no base color cards and no trump or if the base color is trump and the hand contains the jack of trump. if(((!isTrump || hasNoTrump) && hasNoBaseColor) || (baseColor(pkTrick).equals(trump(pkTrick)) && PackedCardSet.isEmpty(PackedCardSet.difference(trumps, jackSingleton)))) return pkHand; if(baseColor(pkTrick) != trump(pkTrick)) if(hasNoBaseColor && !PackedCardSet.isEmpty(allButTrumps)) return PackedCardSet.union(allButTrumps, trumpsAbove); else if(!hasNoBaseColor) return PackedCardSet.union(baseColorCards, trumpsAbove); if((baseColor(pkTrick).equals(trump(pkTrick))) || (hasNoBaseColor && !hasTrumpAbove && PackedCardSet.isEmpty(allButTrumps))) return trumps; if(hasNoBaseColor && !hasTrumpAbove && !PackedCardSet.isEmpty(allButTrumps)) return allButTrumps; if(hasNoBaseColor && hasTrumpAbove) return trumpsAbove; return pkHand; } /** * A method to get the value of the {@link Trick}. * @param pkTrick an integer representing a packed {@link Trick}. * @return the integer value of the {@link Trick}. */ public static int points(int pkTrick) { assert isValid(pkTrick): "Invalid trick in points function of pkTrick"; int pts = 0; for (int i = 0; i < CARD_NBR; i++) if(card(pkTrick, i) != PackedCard.INVALID) pts += PackedCard.points(trump(pkTrick), card(pkTrick, i)); if(isLast(pkTrick)) pts += Jass.LAST_TRICK_ADDITIONAL_POINTS; return pts; } /** * A method that gives the identity of the current winning {@link PlayerId} of the {@link Trick} (supposed non empty). * @param pkTrick an integer representing a packed {@link Trick}. * @return the {@link PlayerId} that is winning the current {@link Trick}. */ public static PlayerId winningPlayer(int pkTrick) { assert isValid(pkTrick): "Invalid trick in winningPlayer function of pkTrick"; return player(pkTrick, winningCardIndex(pkTrick)); } /** * Gives a textual representation of a given trick. * @param pkTrick an integer representing a packed {@link Trick}. * @return a String representing the given trick. */ public static String toString(int pkTrick) { assert isValid(pkTrick): "Invalid trick in toString function of pkTrick"; StringBuilder s = new StringBuilder("Pli ") .append(index(pkTrick)) .append(", commence par ") .append(player(pkTrick, 0).toString()) .append(" : "); for (int i = 0; i < size(pkTrick); i++) if(i == 0) s.append(PackedCard.toString(card(pkTrick, i))); else s.append(", ") .append(PackedCard.toString(card(pkTrick, i))); return s.toString(); } private static int winningCardIndex(int pkTrick) { assert isValid(pkTrick): "Invalid trick in winningCardIndex function of pkTrick"; int winningCardIndex = 0; //Compares all the cards of the trick to find the index of the one better that all the others. for(int i=0 ; i < size(pkTrick)-1 ; ++i) if(PackedCard.isBetter(trump(pkTrick), card(pkTrick, i+1), card(pkTrick, winningCardIndex))) winningCardIndex = (i+1); return winningCardIndex; } private static int firstPlayerIndex(int pkTrick) { assert isValid(pkTrick): "Invalid trick in firstPlayerIndex function of pkTrick"; return Bits32.extract(pkTrick, FIRST_PLAYER_INDEX, FIRST_PLAYER_SIZE); } }