358 lines
14 KiB
Java
358 lines
14 KiB
Java
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);
|
|
}
|
|
|
|
}
|