Add files via upload

This commit is contained in:
charlesbvll 2019-09-30 12:55:33 +02:00 committed by GitHub
parent 89dc6ee958
commit c24b9f3d28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 2268 additions and 0 deletions

174
jass/CardSet.java Normal file
View File

@ -0,0 +1,174 @@
package ch.epfl.javass.jass;
import java.util.List;
import static ch.epfl.javass.Preconditions.*;
/**
* The representation of a {@link CardSet} by a list of {@link Card}.
* @author Charles BEAUVILLE
* @author Celia HOUSSIAUX
*
*/
public final class CardSet {
/**
* An empty {@link CardSet}.
*/
public static final CardSet EMPTY = new CardSet(PackedCardSet.EMPTY);
/**
* A {@link CardSet} containing all {@link Card} of a Jass.
*/
public static final CardSet ALL_CARDS = new CardSet(PackedCardSet.ALL_CARDS);
/**
* A method to get a {@link CardSet} contained in a list of cards.
* @param cards.
* @return a packed version of the {@link CardSet}.
*/
public static CardSet of(List<Card> cards) {
long packed = PackedCardSet.EMPTY;
for(Card card : cards) {
packed = PackedCardSet.add(packed, card.packed());
}
return new CardSet(packed);
}
/**
* A method to get a {@link CardSet} knowing its packed version.
* @param packed, the packed version of the {@link CardSet}.
* @return a {@link CardSet}.
*/
public static CardSet ofPacked(long packed) {
checkArgument(PackedCardSet.isValid(packed));
return new CardSet(packed);
}
private final long packed;
private CardSet(long packed) {
this.packed = packed;
}
/**
* A method to get the packed version of a {@link CardSet}.
* @return a long representing the packed version of the {@link CardSet}.
*/
public long packed() {
return this.packed;
}
/**
* A method to check if the {@link CardSet} contains no {@link Card}.
* @return a boolean true if the {@link CardSet} is empty.
*/
public boolean isEmpty() {
return PackedCardSet.isEmpty(packed);
}
/**
* A method to get the number of {@link Card} in the {@link CardSet}.
* @return an int representing the size of the {@link CardSet}.
*/
public int size() {
return PackedCardSet.size(packed);
}
/**
* A method to obtain the {@link Card} in the {@link CardSet} at the given index.
* @param index an int for the index of the wanted {@link Card}
* @return a {@link Card} in the {@link CardSet} at the given index.
*/
public Card get(int index) {
return Card.ofPacked(PackedCardSet.get(packed, index));
}
/**
* Adds the given {@link Card} to the {@link CardSet}.
* @param card the {@link Card} to add to the {@link CardSet}.
* @return the new {@link CardSet} containing the {@link Card}.
*/
public CardSet add(Card card) {
return new CardSet(PackedCardSet.add(packed, card.packed()));
}
/**
* Removes the given {@link Card} from the {@link CardSet}.
* @param card the {@link Card} to remove from the {@link CardSet}.
* @return the new {@link CardSet} without the given {@link Card}.
*/
public CardSet remove(Card card) {
return new CardSet(PackedCardSet.remove(packed, card.packed()));
}
/**
* Checks if the {@link CardSet} contains the given {@link Card}.
* @param card the {@link Card} to be checked.
* @return whether or not the {@link Card} is contained in {@link CardSet}.
*/
public boolean contains(Card card) {
return PackedCardSet.contains(packed, card.packed());
}
/**
* Computes all the {@link Card}s not contained in {@link CardSet} in form of a new {@link CardSet}.
* @return a new {@link CardSet} containing all the {@link Card} not contained in the {@link CardSet}.
*/
public CardSet complement() {
return new CardSet(PackedCardSet.complement(packed));
}
/**
* Computes the {@link CardSet} containing the {@link Card}s that are in the given {@link CardSet} and in the prior {@link CardSet}.
* @param that the given {@link CardSet} to merge with the prior {@link CardSet}.
* @return a new {@link CardSet} which is the union of the prior {@link CardSet} and the given {@link CardSet}.
*/
public CardSet union(CardSet that) {
return new CardSet(PackedCardSet.union(packed, that.packed()));
}
/**
* Computes the {@link CardSet} containing the {@link Card}s that are only in both the given {@link CardSet} and the prior {@link CardSet}.
* @param that the given {@link CardSet} to intersect with the prior {@link CardSet}.
* @return a new {@link CardSet} which is the intersection of the prior {@link CardSet} and the given {@link CardSet}.
*/
public CardSet intersection(CardSet that) {
return new CardSet(PackedCardSet.intersection(packed, that.packed()));
}
/**
* Computes the {@link CardSet} containing the {@link Card}s of the {@link CardSet} minus the {@link Card}s of the given {@link CardSet}.
* @param that the given {@link CardSet} to be substracted from the prior {@link CardSet}.
* @return a new {@link CardSet} which is the {@link CardSet} minus the given {@link CardSet}.
*/
public CardSet difference(CardSet that) {
return new CardSet(PackedCardSet.difference(packed, that.packed()));
}
/**
* Computes a {@link CardSet} containing only the {@link Card}s of a given {@link Color}.
* @param color the {@link Color} of the wanted subset of {@link CardSet}.
* @return a {@link CardSet} containing only the {@link Card}s of a given {@link Color}.
*/
public CardSet subsetOfColor(Card.Color color) {
return new CardSet(PackedCardSet.subsetOfColor(packed, color));
}
@Override
public boolean equals(Object that) {
return that != null
&& that.getClass() == this.getClass()
&& this.packed == ((CardSet)that).packed();
}
@Override
public int hashCode() {
return Long.hashCode(packed);
}
@Override
public String toString() {
return PackedCardSet.toString(packed);
}
}

30
jass/Jass.java Normal file
View File

@ -0,0 +1,30 @@
package ch.epfl.javass.jass;
/**
* An interface containing the main variables defining a Jass game
* @author Charles BEAUVILLE
* @author Celia HOUSSIAUX
*
*/
public interface Jass {
/**
* Number of cards in one hand at the beginning of a round.
*/
int HAND_SIZE = 9;
/**
* Number of tricks in a round.
*/
int TRICKS_PER_TURN = 9;
/**
* Number of necessary points to win.
*/
int WINNING_POINTS = 1000;
/**
* Number of additional points won by a team which has won all the tricks of a round.
*/
int MATCH_ADDITIONAL_POINTS = 100;
/**
* Number of additional points won by a team winning the last trick.
*/
int LAST_TRICK_ADDITIONAL_POINTS = 5;
}

188
jass/JassGame.java Normal file
View File

@ -0,0 +1,188 @@
package ch.epfl.javass.jass;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import ch.epfl.javass.jass.Card.Color;
import ch.epfl.javass.jass.Card.Rank;
/**
* The representation of a game of Jass.
* @author Charles BEAUVILLE
* @author Celia HOUSSIAUX
*
*/
public final class JassGame {
private static final Card SEVEN_OF_DIAMONDS = Card.of(Color.DIAMOND, Rank.SEVEN);
private final Map<PlayerId, Player> players;
private final Map<PlayerId, String> playerNames;
private final Random shuffleRng;
private final Random trumpRng;
private final Map<Player, CardSet> hands = new HashMap<>();
private final List<Card> deck = new ArrayList<>();
private PlayerId firstPlayerId;
private Color trump;
private TurnState currentTurnState;
private boolean isGameOver = false;
/**
* The representation of a game of Jass.
* @param rngSeed the seed for the random number generator (for the shuffle and the trump).
* @param players a map between the {@link PlayerId}s and the {@link Player}s of the game.
* @param playerNames a map between the {@link Players}s of the game and their names.
*/
public JassGame(long rngSeed, Map<PlayerId, Player> players,
Map<PlayerId, String> playerNames) {
this.players = Collections.unmodifiableMap(new EnumMap<>(players));
this.playerNames = Collections
.unmodifiableMap(new EnumMap<>(playerNames));
Random rng = new Random(rngSeed);
this.shuffleRng = new Random(rng.nextLong());
this.trumpRng = new Random(rng.nextLong());
}
/**
* A method to check if the game is over (if a team has more than 1000).
* @return a boolean which is true if a team has won
*/
public boolean isGameOver() {
return isGameOver;
}
/**
* A method that plays the game until the end of the current {@link Trick}.
*/
public void advanceToEndOfNextTrick() {
if(!isGameOver) {
//Creates the first turn state if it does not exist yet.
if (currentTurnState == null)
newTurn(true);
//Collects the trick when it is full.
if (currentTurnState.trick().isFull())
currentTurnState = currentTurnState.withTrickCollected();
//Creates the next turn if all the tricks of the current turn have been played.
if (currentTurnState.isTerminal())
newTurn(false);
//Calls the methods to update the score for all players.
updateScoreForAll();
//Calls the methods to update the trick for all players.
updateTrickForAll();
//While the trick isn't full ask each player which card they play and update the trick.
while (!currentTurnState.trick().isFull())
updatePlayers();
checkIfTeamWon();
}
}
private void newTurn(boolean isFirst) {
trump = Color.ALL.get(trumpRng.nextInt(Color.COUNT));
initializeAndShuffleDeck();
initializePlayers(isFirst);
if (!isFirst) {
setNewFirstPlayer();
currentTurnState = TurnState.initial(trump,
currentTurnState.score().nextTurn(), firstPlayerId);
} else
currentTurnState = TurnState.initial(trump, Score.INITIAL,
firstPlayerId);
}
private void checkIfTeamWon() {
for (int i = 0; i < TeamId.COUNT; i++) {
if (currentTurnState
.score()
.totalPoints(TeamId.ALL.get(i)) >= Jass.WINNING_POINTS)
updateWinningTeam(TeamId.ALL.get(i));
}
}
private void initializeAndShuffleDeck() {
deck.clear();
for (Color color : Color.ALL)
for (Rank rank : Rank.ALL)
deck.add(Card.of(color, rank));
Collections.shuffle(deck, shuffleRng);
}
private void initializePlayers(boolean first) {
for (Map.Entry<PlayerId, Player> entry : players.entrySet()) {
Player p = entry.getValue();
PlayerId iD = entry.getKey();
if (first)
p.setPlayers(entry.getKey(), playerNames);
//Distributes the cards among players.
hands.put(p, CardSet.of(deck.subList(iD.ordinal() * Jass.HAND_SIZE,
(iD.ordinal() + 1) * Jass.HAND_SIZE)));
p.updateHand(hands.get(p));
p.setTrump(trump);
//Compute which player is the first player i.e has the jack of diamonds.
if (first && hands.get(p)
.contains(SEVEN_OF_DIAMONDS))
firstPlayerId = iD;
}
}
private void updatePlayers() {
PlayerId pid = currentTurnState.nextPlayer();
Player p = players.get(pid);
//Asks the player which card he wants to play.
Card c = p.cardToPlay(currentTurnState, hands.get(p));
//Updates the turn with the card played
currentTurnState = currentTurnState.withNewCardPlayed(c);
//Updates the hand of the player with the card played.
hands.put(p, hands.get(p).remove(c));
p.updateHand(hands.get(p));
//Calls the methods to update the trick for all players.
updateTrickForAll();
}
private void updateTrickForAll() {
for (Player p : players.values())
p.updateTrick(currentTurnState.trick());
}
private void updateScoreForAll() {
for (Player p : players.values())
p.updateScore(currentTurnState.score());
}
private void setNewFirstPlayer() {
firstPlayerId = PlayerId.ALL
.get((firstPlayerId.ordinal() + 1) % PlayerId.COUNT);
}
private void updateWinningTeam(TeamId team) {
isGameOver = true;
for (Player p : players.values()) {
p.updateScore(currentTurnState.score());
p.setWinningTeam(team);
}
}
}

213
jass/MctsPlayer.java Normal file
View File

@ -0,0 +1,213 @@
package ch.epfl.javass.jass;
import static ch.epfl.javass.Preconditions.checkArgument;
import java.util.ArrayList;
import java.util.SplittableRandom;
/**
* A simulated player that plays cards selected by a Monte Carlo search tree Algorithm.
* @author Charles BEAUVILLE
* @author Celia HOUSSIAUX
*
*/
public final class MctsPlayer implements Player {
private final static int EXPLORATION_CONST = 40;
private final SplittableRandom rng;
private final PlayerId id;
private final int iterations;
/**
* A simulated player that plays cards selected by a Monte Carlo search tree Algorithm.
* @param ownId the playerId of the simulated player.
* @param rngSeed a long the seed for the pseudo-random number generator used to simulate random turns.
* @param iterations the number of iteration of the algorithm, must be superior to the size of a hand of Jass.
* @throws IllegalArgumentException if the number of iterations is inferior to 9.
*/
public MctsPlayer(PlayerId ownId, long rngSeed, int iterations) {
checkArgument(iterations >= Jass.HAND_SIZE);
this.rng = new SplittableRandom(rngSeed);
this.id = ownId;
this.iterations = iterations;
}
@Override
public Card cardToPlay(TurnState state, CardSet hand) {
Node root = new Node(state, playableCards(state, hand.packed(), id), id);
ArrayList<Node> path;
for (int i = 0; i < iterations; ++i) {
//Add nodes to the path.
path = root.addNode(hand.packed());
//Simulate the last node of the path.
long score = simulate(path.get(path.size()-1), hand.packed());
//Propagate the score through all the nodes.
backPropagateScore(score, path);
}
return Card.ofPacked(PackedCardSet.get(playableCards(state, hand.packed(), id), root.highestV(0)));
}
/*
* Simulate the possible score of a random turn given a certain state and hand.
* Returns the final score of a finished random turn.
*/
private long simulate(Node node, long hand) {
TurnState s = node.state;
//Plays a random turn from the state of the given node.
if(s.isTerminal())
return s.packedScore();
else
while (!s.isTerminal()) {
long pC = playableCards(s, hand, id);
Card randomCard = Card.ofPacked(PackedCardSet.get(pC, rng.nextInt(PackedCardSet.size(pC))));
s = s.withNewCardPlayedAndTrickCollected(randomCard);
}
return s.packedScore();
}
/*
* Determines the playable cards for a given state taking account of the non-played cards and the hand of the payer.
* Returns the playable cards for state.
*/
private static long playableCards(TurnState state, long hand, PlayerId id) {
if (state.isTerminal())
return PackedCardSet.EMPTY;
return state.nextPlayer().equals(id) ?
PackedTrick.playableCards(state.packedTrick(), PackedCardSet.intersection(hand, state.packedUnplayedCards())) :
PackedTrick.playableCards(state.packedTrick(), PackedCardSet.difference(state.packedUnplayedCards(), hand));
}
/*
* A method that propagate the updated score along the path of nodes by updating the score of every nodes.
*/
private void backPropagateScore(long score, ArrayList<Node> path) {
path.get(0).updateScore(score, id.team());
for (int i = 1; i < path.size(); ++i)
path.get(i).updateScore(score,
path.get(i - 1)
.state.nextPlayer()
.team());
}
/**
* A node of the Monte Carlo Tree Search algorithm.
* @author Charles Beauville
* @author Célia Houssiaux
*
*/
private static final class Node {
private final TurnState state;
private long potentialCards;
private final Node[] children;
private int n;
private int s;
private PlayerId id;
private Node(TurnState state, long playableCards, PlayerId id) {
this.state = state;
this.potentialCards = playableCards;
this.s = 0;
this.n = 0;
this.children = new Node[PackedCardSet.size(potentialCards)];
this.id = id;
}
/*
* A method that gives the index of the best child of a Node.
* That means the one who got the highest value of V.
* c is the exploration constant.
* Returns the index of the best child in children[].
*/
private int highestV(int c) {
int bestVIndex = 0;
double v;
double bestV = 0;
for (int i = 0; i < children.length; i++) {
if (children[i] == null)
return i;
//Computes the function V.
if (children[i].n > 0)
v = (double) (children[i].s) / (double)(children[i].n)
+ c * Math.sqrt((Math.log(n+1) / (double)children[i].n));
else
return i;
//Computes if the node is the new best node.
if (v > bestV) {
bestV = v;
bestVIndex = i;
}
}
return bestVIndex;
}
/*
* A method that adds if possible a new node at the right place of the tree.
* Returns a List that contains the path from the root to the new node freshly created.
*/
private ArrayList<Node> addNode(long hand) {
ArrayList<Node> path = new ArrayList<>();
boolean newNodeAdded = false;
path.add(this);
Node lastNode = this;
//Goes through all the best children of the last node and adds them to the path until a node does not have all its children.
while (lastNode.hasAllChildren()) {
lastNode = lastNode.children[lastNode.highestV(EXPLORATION_CONST)];
path.add(lastNode);
if (lastNode.state.isTerminal())
return path;
}
//Creates a new node as a child of last node
if (!PackedCardSet.isEmpty(lastNode.potentialCards)) {
//Computes a new turn state with the first card of the potential cards played and removes the played card form the potential cards.
int card = PackedCardSet.get(lastNode.potentialCards, 0);
TurnState newState = lastNode.state
.withNewCardPlayedAndTrickCollected(Card.ofPacked(card));
lastNode.potentialCards = PackedCardSet.remove(lastNode.potentialCards, card);
long newPotentialCards = playableCards(newState, hand, id);
if (PackedCardSet.isEmpty(newPotentialCards))
return path;
Node node = new Node(newState, newPotentialCards, id);
//Adds the new computed node to the children of this node where there is space.
for (int i = 0; i < lastNode.children.length; i++) {
if (newNodeAdded)
break;
if (lastNode.children[i] == null) {
lastNode.children[i] = node;
newNodeAdded = true;
}
}
}
return path;
}
/*
* This boolean indicates if a children[]is full.
* Returns true, if children[] is full.
*/
private boolean hasAllChildren() {
return children[children.length-1] != null;
}
/*
* This method updates the score of a given team.
*/
private void updateScore(long score, TeamId t) {
s += PackedScore.totalPoints(score, t);
n++;
}
}
}

80
jass/PacedPlayer.java Normal file
View File

@ -0,0 +1,80 @@
package ch.epfl.javass.jass;
import java.util.Map;
import ch.epfl.javass.jass.Card.Color;
import static ch.epfl.javass.Preconditions.checkArgument;
/**
* A player that slows down the game
* @author Charles BEAUVILLE
* @author Celia HOUSSIAUX
*
*/
public final class PacedPlayer implements Player {
private final Player underlyingPlayer;
private final long minTime;
/**
* A player that slows down the game
* @param underlyingPlayer the {@link Player} associated with this {@link PacedPlayer}.
* @param minTime a double the minimum time in seconds to wait for the {@link Player}.
*/
public PacedPlayer(Player underlyingPlayer, double minTime) {
checkArgument(minTime >= 0);
this.underlyingPlayer = underlyingPlayer;
this.minTime = (long) minTime * 1000;
}
@Override
public Card cardToPlay(TurnState state, CardSet hand) {
long start = System.currentTimeMillis();
Card c = underlyingPlayer.cardToPlay(state, hand);
long end = System.currentTimeMillis();
long timeElapsed = (end - start);
if (timeElapsed >= minTime)
return c;
else
try {
Thread.sleep((minTime - timeElapsed));
} catch (InterruptedException e) {
/* ignore */ }
return c;
}
@Override
public void setPlayers(PlayerId ownId, Map<PlayerId, String> playerNames) {
underlyingPlayer.setPlayers(ownId, playerNames);
}
@Override
public void updateHand(CardSet newHand) {
underlyingPlayer.updateHand(newHand);
}
@Override
public void setTrump(Color trump) {
underlyingPlayer.setTrump(trump);
}
@Override
public void updateTrick(Trick newTrick) {
underlyingPlayer.updateTrick(newTrick);
}
@Override
public void updateScore(Score score) {
underlyingPlayer.updateScore(score);
}
@Override
public void setWinningTeam(TeamId winningTeam) {
underlyingPlayer.setWinningTeam(winningTeam);
}
}

165
jass/PackedCard.java Normal file
View File

@ -0,0 +1,165 @@
package ch.epfl.javass.jass;
import ch.epfl.javass.bits.Bits32;
import ch.epfl.javass.jass.Card.Color;
import ch.epfl.javass.jass.Card.Rank;
/**
* Allows us to manipulate cards from a Jass game packed in an integer.
*
* @author Charles BEAUVILLE
* @author Celia HOUSSIAUX
*
*/
public final class PackedCard {
private PackedCard() {
}
/**
* Contains the binary value 111111, which represents an invalid packed card
*/
public static final int INVALID = 0b111111;
/**
* Returns the packed card having the proper {@link Color} and {@link Rank}.
*
* @param c
* the {@link Color} of the wanted packed {@link Card}.
* @param r
* the {@link Rank} of the wanted packed {@link Card}.
* @return the packed {@link Card} version of the given {@link Rank} and
* {@link Color}.
*/
public static int pack(Color c, Rank r) {
return Bits32.pack(r.ordinal(), RANK_SIZE, c.ordinal(), COLOR_SIZE);
}
private static final int RANK_INDEX = 0;
private static final int COLOR_INDEX = 4;
private static final int MAX_RANK_VALUE = 8;
private static final int RANK_SIZE = 4;
private static final int COLOR_SIZE = 2;
private static final int UNUSED_SIZE = 25;
private static final int INDEX_TRUMP = 0;
private static final int INDEX_NORMAL = 1;
private static final int[][] PTS = { { 0, 0 }, { 0, 0 }, { 0, 0 },
{ 14, 0 }, { 10, 10 }, { 20, 2 }, { 3, 3 }, { 4, 4 }, { 11, 11 } };
/**
* Returns true only if the value is a valid packed {@link Card}, which
* means if the bits containing the rank contain a value between 0 and
* 8(included) and if the useless bits are all equal to 0.
*
* @param pkCard
* an int representing the packed version of a Jass {@link Card}.
* @return a boolean true if the packed version of the {@link Card} is valid
*/
public static boolean isValid(int pkCard) {
return (Bits32.extract(pkCard, RANK_INDEX, RANK_SIZE) <= MAX_RANK_VALUE
&& Bits32.extract(pkCard, RANK_INDEX, RANK_SIZE) >= 0
&& Bits32.extract(pkCard, RANK_SIZE + COLOR_SIZE,
UNUSED_SIZE) == 0);
}
/**
* Returns the {@link Color} of the packed {@link Card}.
*
* @param pkCard
* an int representing the packed version of a Jass {@link Card}.
* @return the {@link Color} of the given packed {@link Card}.
*/
public static Color color(int pkCard) {
assert isValid(pkCard) : "Invalid card in color function of pkCard";
int pkColor = Bits32.extract(pkCard, COLOR_INDEX, COLOR_SIZE);
return Color.ALL.get(pkColor);
}
/**
* Returns the {@link Rank} of the packed {@link Card}.
*
* @param pkCard
* an int representing the packed version of a Jass {@link Card}.
* @return the {@link Rank} of the given packed {@link Card}.
*/
public static Rank rank(int pkCard) {
assert isValid(pkCard) : "Invalid card in rank function of pkCard";
int pkRank = Bits32.extract(pkCard, RANK_INDEX, RANK_SIZE);
return Rank.ALL.get(pkRank);
}
/**
* Compares the first {@link Card} entered with the second one, knowing that
* the {@link Color} is trump.
*
* @param trump
* the trump {@link Color}
* @param pkCardL
* the {@link Card} to be compared with
* @param pkCardR
* the {@link Card} to be compared to
* @return a boolean which is true only if the first {@link Card} entered is
* superior to the second one, knowing that trump is the asset.
*/
public static boolean isBetter(Color trump, int pkCardL, int pkCardR) {
assert isValid(
pkCardL) : "Invalid pkCardL in isBetter function of pkCard";
assert isValid(
pkCardR) : "Invalid pkCardR in isBetter function of pkCard";
boolean bothTrump = color(pkCardL).equals(trump) && color(pkCardR).equals(trump);
boolean betterTrumpRank = rank(pkCardL).trumpOrdinal() > rank(pkCardR).trumpOrdinal();
boolean betterRank = rank(pkCardL).ordinal() > rank(pkCardR).ordinal();
boolean bothNotTrump = !color(pkCardL).equals(trump) && !color(pkCardR).equals(trump);
boolean sameColor = color(pkCardL).equals(color(pkCardR));
if(sameColor)
return bothTrump ? betterTrumpRank : betterRank;
else
return !bothNotTrump ? color(pkCardL).equals(trump) : false;
}
/**
* Returns the value of the packed {@link Card}.
*
* @param trump
* the trump {@link Color}.
* @param pkCard
* an int representing the packed version of a Jass {@link Card}.
* @return the value of the given packed {@link Card}.
*/
public static int points(Color trump, int pkCard) {
assert isValid(pkCard) : "Invalid card in points function of pkCard";
return color(pkCard).equals(trump)
? PTS[rank(pkCard).ordinal()][INDEX_TRUMP]
: PTS[rank(pkCard).ordinal()][INDEX_NORMAL];
}
/**
* Returns a representation of the packed {@link Card} under a string of
* character containing the symbol of the {@link Color} and the shorted name
* of the {@link Rank}.
*
* @param pkCard
* an int representing the packed version of a Jass {@link Card}.
* @return a string the description of the given packed {@link Card} (its
* {@link Rank} and its {@link Color}).
*/
public static String toString(int pkCard) {
assert isValid(pkCard) : "Invalid card in toString function of pkCard";
return color(pkCard).toString() +
rank(pkCard).toString();
}
}

236
jass/PackedCardSet.java Normal file
View File

@ -0,0 +1,236 @@
package ch.epfl.javass.jass;
import ch.epfl.javass.jass.Card.Rank;
import static ch.epfl.javass.bits.Bits64.mask;
import java.util.StringJoiner;
/**
* The packed representation of a card set
* @author Charles BEAUVILLE
* @author Celia HOUSSIAUX
*
*/
public final class PackedCardSet {
private PackedCardSet() {}
private static final int COLOR_SIZE = 16;
private static final int SPADES_INDEX = 0;
private static final int HEARTS_INDEX = 16;
private static final int DIAMONDS_INDEX = 32;
private static final int CLUBS_INDEX = 48;
/**
* The packed version of an empty {@link CardSet}.
*/
public static final long EMPTY = 0L;
/**
* The packed version off a {@link CardSet} containing all {@link Card}s of jass.
*/
public static final long ALL_CARDS = mask(CLUBS_INDEX, Rank.COUNT) | mask(DIAMONDS_INDEX, Rank.COUNT) | mask(HEARTS_INDEX, Rank.COUNT) | mask(SPADES_INDEX, Rank.COUNT);
private static final long[] trumpTab =
{0b111111110L,
0b111111100L,
0b111111000L,
0b000100000L,
0b111101000L,
0b000000000L,
0b110101000L,
0b100101000L,
0b000101000L};
private static final long[] colorTab = {mask(SPADES_INDEX, Rank.COUNT), mask(HEARTS_INDEX, Rank.COUNT), mask(DIAMONDS_INDEX, Rank.COUNT), mask(CLUBS_INDEX, Rank.COUNT)};
/**
* Returns true if and only if the given {@link PackedCard}set is valid, none of the unused bits are equal to 1
* @param {@link pkCardSet} the packed version of a {@link CardSet} to be checked
* @return a boolean whether or not the given {@link pkCardSet} is valid
*/
public static boolean isValid(long pkCardSet) {
return (pkCardSet | ALL_CARDS) == ALL_CARDS
&& pkCardSet >= 0
&& pkCardSet <= ALL_CARDS;
}
/**
* Returns the packed version of a {@link CardSet} containing all {@link Card}s of {@link Color} trump above the given {@link packedCard}
* @param pkCard the {@link packedCard} version of a {@link Card} you want to find which trumps are above
* @return a long which represents the packed version of the {@link CardSet} of trump cards above the given card
*/
public static long trumpAbove(int pkCard) {
assert PackedCard.isValid(pkCard): "Invalid card set in trumpAbove function of pkCardSet";
return trumpTab[PackedCard.rank(pkCard).ordinal()] << PackedCard.color(pkCard).ordinal() * COLOR_SIZE;
}
/**
* Returns the packed version of the {@link CardSet} containing only the given {@link packedCard}
* @param pkCard the packed version of the {@link Card} from which the {@link CardSet} is created
* @return a long the packed version of a {@link CardSet} containing the given {@link Card}
*/
public static long singleton(int pkCard) {
assert PackedCard.isValid(pkCard): "Invalid card set in singleton function of pkCardSet";
return 1L << pkCard;
}
/**
* Checks if the given {@link pkCardSet} is empty or not (ie contains no {@link Card})
* @param pkCardSet a long representing the {@link CardSet} to check
* @return a boolean true only if the {@link CardSet} is empty(ie all his bits are null)
*/
public static boolean isEmpty(long pkCardSet) {
assert isValid(pkCardSet): "Invalid card set in isEmpty function of pkCardSet";
return pkCardSet == EMPTY;
}
/**
* Computes the size of the given {@link pkCardSet} (ie the number of {@link Card}s it contains)
* @param pkCardSet a long representing the packed version of a {@link CardSet}
* @return an int the size of the {@link CardSet} (ie the number of {@link Card}s it contains)
*/
public static int size(long pkCardSet) {
assert isValid(pkCardSet): "Invalid card set in size function of pkCardSet";
return Long.bitCount(pkCardSet);
}
/**
* Gives the packed version of the {@link Card} of the given index in the given {@link CardSet}
* @param pkCardSet a long representing the {@link pkCardSet} to get the {@link Card} from
* @param index an int, the index of the {@link Card} to extract from the {@link CardSet}
* @return an int representing the packed version of a {@link Card}
*/
public static int get(long pkCardSet, int index) {
assert isValid(pkCardSet): "Invalid card set in get function of pkCardSet";
assert (index < size(pkCardSet) && index >= 0): "Invalid index in get function of pkCardSet";
for (int i = 1; i <= index; i++)
pkCardSet = pkCardSet ^ Long.lowestOneBit(pkCardSet);
return Long.numberOfTrailingZeros(pkCardSet);
}
/**
* Adds a {@link packedCard} to the packed version of a {@link CardSet}
* @param pkCardSet a long representing the packed version of a {@link CardSet}
* @param pkCard an int representing the packed version of a {@link Card}
* @return a long the {@link Card} set after the given {@link Card} has been added
*/
public static long add(long pkCardSet, int pkCard) {
assert isValid(pkCardSet): "Invalid card set in add function of pkCardSet";
assert PackedCard.isValid(pkCard): "Invalid card in add function of pkCardSet";
return pkCardSet | singleton(pkCard) ;
}
/**
* Deletes a given {@link packedCard} from a {@link pkCardSet}
* @param pkCardSet a long representing the packed version of a {@link CardSet}
* @param pkCard an int representing a {@link packedCard}
* @return a long the {@link CardSet} after the given {@link Card} has been removed
*/
public static long remove(long pkCardSet, int pkCard) {
assert isValid(pkCardSet): "Invalid card set in remove function of pkCardSet";
assert PackedCard.isValid(pkCard): "Invalid card in remove function of pkCardSet";
return pkCardSet & ~singleton(pkCard);
}
/**
* Checks if the given {@link packedCard} is in the given {@link pkCardSet}
* @param pkCardSet a long representing a {@link pkCardSet}
* @param pkCard an int representing a {@link packedCard}
* @return a boolean true if the given card is in the given {@link CardSet}
*/
public static boolean contains(long pkCardSet, int pkCard) {
assert isValid(pkCardSet): "Invalid card set in contains function of pkCardSet";
assert PackedCard.isValid(pkCard): "Invalid card in contains function of pkCardSet";
return (pkCardSet & singleton(pkCard)) != EMPTY;
}
/**
* Computes the inverse of the given p{@link pkCardSet} meaning all the {@link Card}s that are not in the given {@link CardSet}
* @param pkCardSet a long representing a {@link pkCardSet}
* @return a long representing the packed version of the {@link CardSet} containing all the {@link Card}s that were not in the given {@link CardSet}
*/
public static long complement(long pkCardSet) {
assert isValid(pkCardSet): "Invalid card set in complement function of pkCardSet";
return pkCardSet ^ ALL_CARDS ;
}
/**
* Computes the union of the given {@link CardSet}s meaning the {@link CardSet} which contains all the {@link Card}s from both {@link CardSet}s
* @param pkCardSet1 a long representing the packed version of a {@link CardSet}
* @param pkCardSet2 a long representing the packed version of a {@link CardSet}
* @return a long which represents a set containing all the {@link Card}s for both {@link CardSet}s
*/
public static long union(long pkCardSet1, long pkCardSet2) {
assert isValid(pkCardSet1): "Invalid card set 1 in union function of pkCardSet";
assert isValid(pkCardSet2): "Invalid card set 2 in union function of pkCardSet";
return pkCardSet1 | pkCardSet2;
}
/**
* Computes the intersection of the given {@link CardSet}s meaning the {@link CardSet} which contains the {@link Card}s that are in both {@link CardSet}s
* @param pkCardSet1 a long representing the packed version of a {@link CardSet}
* @param pkCardSet2 a long representing the packed version of a {@link CardSet}
* @return a long which represents a {@link CardSet} containing the {@link Card}s that are in both {@link CardSet}s
*/
public static long intersection(long pkCardSet1, long pkCardSet2) {
assert isValid(pkCardSet1): "Invalid card set 1 in intersection function of pkCardSet";
assert isValid(pkCardSet2): "Invalid card set 2 in intersection function of pkCardSet";
return pkCardSet1 & pkCardSet2;
}
/**
* Computes the difference between the two given {@link CardSet}s meaning the {@link CardSet} which contains the {@link Card}s that are in the first but not in the second one
* @param pkCardSet1 a long representing the packed version of a {@link CardSet}
* @param pkCardSet2 a long representing the packed version of a {@link CardSet}
* @return a long which represents a {@link CardSet} containing the {@link Card}s that are in the first but not in the second one
*/
public static long difference(long pkCardSet1, long pkCardSet2) {
assert isValid(pkCardSet1): "Invalid card set 1 in difference function of pkCardSet";
assert isValid(pkCardSet2): "Invalid card set 2 in difference function of pkCardSet";
return pkCardSet1 & complement(pkCardSet2);
}
/**
* Computes the packed version of a {@link CardSet} containing only the {@link Card}s of the given {@link Color}.
* @param pkCardSet a long representing the packed version of a {@link CardSet}
* @param color the {@link Color} of the wanted subset of the {@link CardSet}.
* @return a long the packed version of a {@link CardSet} containing only the {@link Card}s of the given {@link Color}.
*/
public static long subsetOfColor(long pkCardSet, Card.Color color) {
assert isValid(pkCardSet): "Invalid card set in subsetOfColor function of pkCardSet";
return pkCardSet & colorTab[color.ordinal()] ;
}
/**
* Gives a textual representation of a set of cards
* @param pkCardSet a long representing the packed version of a set of cards
* @return a string describing the given packed set of cards
*/
public static String toString(long pkCardSet) {
assert isValid(pkCardSet): "Invalid card set in toString function of pkCardSet";
StringJoiner sJ = new StringJoiner(",", "{", "}");
for(int i = 0; i < size(pkCardSet); ++i)
sJ.add(PackedCard.toString(get(pkCardSet, i)));
return sJ.toString();
}
}

206
jass/PackedScore.java Normal file
View File

@ -0,0 +1,206 @@
package ch.epfl.javass.jass;
import static ch.epfl.javass.Preconditions.checkArgument;
import ch.epfl.javass.bits.Bits32;
import ch.epfl.javass.bits.Bits64;
/**
* The representation of a score of the game of Jass in a packed form (as a long).
* @author Charles BEAUVILLE
* @author Celia HOUSSIAUX
*
*/
public final class PackedScore {
private PackedScore() {}
/**
* A long representing the initial score.
*/
public static final long INITIAL = 0b000000;
/**
* Packs the six components of a {@link Score} in one long.
* @param turnTricks1 number of tricks gained by team1 during the current turn.
* @param turnPoints1 number of points gained by team1 during the current turn.
* @param gamePoints1 number of points gained by team1 during the previous turns (excluding the current turn).
* @param turnTricks2 number of tricks gained by team2 during the current turn.
* @param turnPoints2 number of points gained by team2 during the current turn.
* @param gamePoints2 number of points gained by team2 during the previous turns (excluding the current turn).
* @throws IllegalArgumentException if turnTricks1,turnPoints1,gamePoints1 && turnTricks2,turnPoints2,gamePoints2 are non valid.
* @return a long the packed version of the scores of both team.
*/
public static long pack(int turnTricks1, int turnPoints1, int gamePoints1, int turnTricks2, int turnPoints2, int gamePoints2) {
checkArgument( areArgumentsValid(turnTricks1,turnPoints1,gamePoints1) && areArgumentsValid(turnTricks2,turnPoints2,gamePoints2));
int t1Score = pack32(turnTricks1,turnPoints1,gamePoints1);
int t2Score = pack32(turnTricks2, turnPoints2, gamePoints2);
return Bits64.pack(t1Score, Integer.SIZE, t2Score, Integer.SIZE);
}
private static final int TRICK_COUNT_INDEX = 0;
private static final int TURN_PTS_INDEX = 4;
private static final int GAME_PTS_INDEX = 13;
private static final int TRICK_COUNT_SIZE = 4;
private static final int TURN_PTS_SIZE = 9;
private static final int GAME_PTS_SIZE = 11;
private static final int UNUSED_SIZE = 8;
private static final int INITIAL_TRICK_COUNT = 0;
private static final int INITIAL_TURN_POINTS = 0;
private static final int MAX_TURN_PTS = 257;
private static final int MAX_GAME_PTS = 2000;
/**
* Check if the value of the packed score is valid.
* @param pkScore a long the packed version of a score.
* @return a boolean true if the given packed score is valid.
*/
public static boolean isValid(long pkScore) {
long nbTricks1 = Bits64.extract(pkScore, TRICK_COUNT_INDEX, TRICK_COUNT_SIZE);
long nbTricks2 = Bits64.extract(pkScore, Integer.SIZE + TRICK_COUNT_INDEX, TRICK_COUNT_SIZE);
long turnPts1 = Bits64.extract(pkScore, TURN_PTS_INDEX, TURN_PTS_SIZE);
long turnPts2 = Bits64.extract(pkScore, Integer.SIZE + TURN_PTS_INDEX, TURN_PTS_SIZE);
long gamePts1 = Bits64.extract(pkScore, GAME_PTS_INDEX, GAME_PTS_SIZE);
long gamePts2 = Bits64.extract(pkScore, Integer.SIZE + GAME_PTS_INDEX, GAME_PTS_SIZE);
long unusedBits1 = Bits64.extract(pkScore, GAME_PTS_INDEX + GAME_PTS_SIZE, UNUSED_SIZE);
long unusedBits2 = Bits64.extract(pkScore, Integer.SIZE + GAME_PTS_INDEX + GAME_PTS_SIZE, UNUSED_SIZE);
return ((nbTricks1 <= Jass.TRICKS_PER_TURN)
&& (turnPts1 <= MAX_TURN_PTS)
&& (gamePts1 <= MAX_GAME_PTS)
&& (unusedBits1 == 0)
&& (nbTricks2 <= Jass.TRICKS_PER_TURN)
&& (turnPts2 <= MAX_TURN_PTS)
&& (gamePts2 <= MAX_GAME_PTS)
&& (unusedBits2 == 0));
}
/**
* Gives the number of {@link Trick}s made by a given team in the current turn.
* @param pkScore a long the packed version of a {@link Score}.
* @param t a {@link TeamId} the team of which we want the number of {@link Trick}s.
* @return an int the number of {@link Trick}s gained by team t during the current turn.
*/
public static int turnTricks(long pkScore, TeamId t) {
assert isValid(pkScore): "Invalid pkScore in turnTricks function of PackedScore";
return t == TeamId.TEAM_1 ?
(int) Bits64.extract(pkScore, TRICK_COUNT_INDEX, TRICK_COUNT_SIZE) :
(int) Bits64.extract(pkScore, Integer.SIZE + TRICK_COUNT_INDEX, TRICK_COUNT_SIZE);
}
/**
* Gives the number of points made by a given team in the current turn.
* @param pkScore a long the packed version of a {@link Score}.
* @param t a {@link TeamId} the team of which we want the number of points made durring the turn.
* @return an int the number of points gained by team t during the current turn.
*/
public static int turnPoints(long pkScore, TeamId t) {
assert isValid(pkScore): "Invalid pkScore in turnPoints function of PackedScore";
return t == TeamId.TEAM_1 ?
(int) Bits64.extract(pkScore, TURN_PTS_INDEX, TURN_PTS_SIZE) :
(int) Bits64.extract(pkScore, Integer.SIZE + TURN_PTS_INDEX, TURN_PTS_SIZE);
}
/**
* Gives the number of points made by a given team before the current turn.
* @param pkScore a long the packed version of a {@link Score} .
* @param t a {@link TeamId} the team of which we want the number of points.
* @return an int the number of points gained by team t before the current turn.
*/
public static int gamePoints(long pkScore, TeamId t) {
assert isValid(pkScore): "Invalid pkScore in gamePoints function of PackedScore";
return t == TeamId.TEAM_1 ?
(int) Bits64.extract(pkScore, GAME_PTS_INDEX, GAME_PTS_SIZE) :
(int) Bits64.extract(pkScore, Integer.SIZE + GAME_PTS_INDEX, GAME_PTS_SIZE);
}
/**
* Gives the number of points made by a given team in the current turn and before.
* @param pkScore a long the packed version of a {@link Score}.
* @param t a {@link TeamId} the team of which we want the number of points.
* @return an int the number of points gained by team t during the current turn and before.
*/
public static int totalPoints(long pkScore, TeamId t) {
assert isValid(pkScore): "Invalid pkScore in totalPoints function of PackedScore";
return t == TeamId.TEAM_1 ?
gamePoints(pkScore,TeamId.TEAM_1) + turnPoints(pkScore,TeamId.TEAM_1) :
gamePoints(pkScore,TeamId.TEAM_2) + turnPoints(pkScore,TeamId.TEAM_2);
}
/**
* Computes the {@link Score} of the team after a {@link Trick} has been played.
* @param pkScore the packed version of a {@link Score}.
* @param winningTeam a {@link TeamId} the team that won the {@link Trick}.
* @param trickPoints the amount of points the {@link Trick} is worth.
* @throws IllegalArgumentException if trickPoints is negative.
* @return a long the updated {@link packedScore}.
*/
public static long withAdditionalTrick(long pkScore, TeamId winningTeam, int trickPoints) {
assert isValid(pkScore): "Invalid pkScore in withAdditionalTrick function of PackedScore";
checkArgument(trickPoints >=0);
int winningTurnTricks = turnTricks(pkScore,winningTeam) + 1;
int winningTurnPoints = turnPoints(pkScore,winningTeam) + trickPoints;
int winningGamePoints = gamePoints(pkScore,winningTeam);
int otherTurnTricks = turnTricks(pkScore,winningTeam.other());
int otherTurnPoints = turnPoints(pkScore,winningTeam.other());
int otherGamePoints = gamePoints(pkScore,winningTeam.other());
if(winningTurnTricks == Jass.TRICKS_PER_TURN)
winningTurnPoints += Jass.MATCH_ADDITIONAL_POINTS;
return winningTeam == TeamId.TEAM_1 ?
pack(winningTurnTricks, winningTurnPoints, winningGamePoints, otherTurnTricks, otherTurnPoints, otherGamePoints) :
pack(otherTurnTricks, otherTurnPoints, otherGamePoints, winningTurnTricks, winningTurnPoints, winningGamePoints);
}
/**
* Gives the packed version of the {@link Score} at the beginning of the next turn.
* @param pkScore the packed version of the {@link Score}.
* @return a long the updated {@link packedScore} for the next turn.
*/
public static long nextTurn(long pkScore) {
assert isValid(pkScore): "Invalid pkScore in nextTurn function of PackedScore";
return pack(INITIAL_TRICK_COUNT, INITIAL_TURN_POINTS, totalPoints(pkScore,TeamId.TEAM_1),
INITIAL_TRICK_COUNT, INITIAL_TURN_POINTS, totalPoints(pkScore,TeamId.TEAM_2));
}
/**
* Gives a textual representation of the {@link Score}.
* @param pkScore a long the packed version of the {@link Score}.
* @return a string the representation of a {@link Score}.
*/
public static String toString(long pkScore) {
assert isValid(pkScore): "Invalid pkScore in toString function of PackedScore";
return ("(" + Integer.toUnsignedString(turnTricks(pkScore,TeamId.TEAM_1)) +
"," + Integer.toUnsignedString(turnPoints(pkScore,TeamId.TEAM_1)) +
"," + Integer.toUnsignedString(gamePoints(pkScore,TeamId.TEAM_1)) +
")/(" + Integer.toUnsignedString(turnTricks(pkScore,TeamId.TEAM_2)) +
"," + Integer.toUnsignedString(turnPoints(pkScore,TeamId.TEAM_2)) +
"," + Integer.toUnsignedString(gamePoints(pkScore,TeamId.TEAM_2)) + ")");
}
private static int pack32(int turnTricks, int turnPoints, int gamePoints) {
return Bits32.pack(turnTricks, TRICK_COUNT_SIZE, turnPoints, TURN_PTS_SIZE, gamePoints, GAME_PTS_SIZE);
}
private static boolean areArgumentsValid(int turnTricks, int turnPoints, int gamePoints) {
return ((turnTricks >= 0) && (turnTricks <= Jass.TRICKS_PER_TURN) &&
(turnPoints >= 0) && (turnPoints <= MAX_TURN_PTS) &&
(gamePoints >= 0) && (gamePoints <= MAX_GAME_PTS));
}
}

357
jass/PackedTrick.java Normal file
View File

@ -0,0 +1,357 @@
package ch.epfl.javass.jass;
import ch.epfl.javass.jass.Card.Color;
import ch.epfl.javass.jass.Card.Rank;
import ch.epfl.javass.bits.Bits32;
import static ch.epfl.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);
}
}

67
jass/Player.java Normal file
View File

@ -0,0 +1,67 @@
package ch.epfl.javass.jass;
import java.util.Map;
import ch.epfl.javass.jass.Card.Color;
/**
* An interface that represent a player of a game of Jass.
* @author Charles BEAUVILLE
* @author Celia HOUSSIAUX
*
*/
public interface Player {
/**
* A method to choose which card to play
* @param state the current TurnState of the game
* @param hand the current hand of the Player
* @return the card to be played
*/
abstract Card cardToPlay(TurnState state, CardSet hand);
/**
* Associates the Players with their name
* @param ownId the iD of the Player
* @param playerNames a map that associates the PlayerId with the names of the players
*/
default void setPlayers(PlayerId ownId, Map<PlayerId, String>playerNames) {
}
/**
* Updates the hand of the player with the new current hand
* @param newHand
*/
default void updateHand(CardSet newHand) {
}
/**
* Updates the trump color of the player with the new current trump
* @param trump
*/
default void setTrump(Color trump) {
}
/**
* Updates the trick of the player with the new current trick
* @param newTrick
*/
default void updateTrick(Trick newTrick) {
}
/**
* Updates the score of the player with the new current score
* @param score
*/
default void updateScore(Score score) {
}
/**
* Sets the winning of the game
* @param winningTeam the team that won the game
*/
default void setWinningTeam(TeamId winningTeam) {
}
}

37
jass/PlayerId.java Normal file
View File

@ -0,0 +1,37 @@
package ch.epfl.javass.jass;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @author Charles BEAUVILLE
* @author Celia HOUSSIAUX
*
*/
public enum PlayerId {
PLAYER_1,
PLAYER_2,
PLAYER_3,
PLAYER_4;
/**
* The total number of players.
*/
public static final int COUNT = 4;
/**
* The list of all players
*/
public static final List<PlayerId> ALL = Collections.unmodifiableList(Arrays.asList(values()));
/**
* Gives the team of the player
* @return a TeamId the team of the player
*/
public TeamId team() {
return this.equals(PLAYER_1) || this.equals(PLAYER_3) ?
TeamId.TEAM_1 :
TeamId.TEAM_2;
}
}

114
jass/Score.java Normal file
View File

@ -0,0 +1,114 @@
package ch.epfl.javass.jass;
import static ch.epfl.javass.Preconditions.*;
/**
*
* @author Charles BEAUVILLE
* @author Celia HOUSSIAUX
*
*/
public final class Score {
public static final Score INITIAL = new Score(PackedScore.INITIAL);
/**
* Check if the packed score is valid.
* @param a long packed the packed score of the team
* @throws IllegalArgumentException if packed is not valid.
* @return the score associated with packed.
*/
public static Score ofPacked(long packed) {
checkArgument(PackedScore.isValid(packed));
Score s = new Score(packed);
return s;
}
private final long packed;
private Score(long packed) {
this.packed = packed;
}
/**
* Gives the packed version of the Score.
* @return a long representing the packed version of the score.
*/
public long packed() {
return packed;
}
/**
* Gives the number of tricks made by a given team in the current turn.
* @param t the teamId for the wanted number of tricks.
* @return an int the number of tricks gained by the team during the receiver's current turn.
*/
public int turnTricks(TeamId t) {
return PackedScore.turnTricks(packed, t);
}
/**
* Gives the number of points made by a given team in the current turn.
* @param t the team.
* @return an int the number of points gained by the team during the receiver's current turn.
*/
public int turnPoints(TeamId t) {
return PackedScore.turnPoints(packed, t);
}
/**
* Gives the number of points made by a given team before the current turn.
* @param t the team.
* @return an int the number of points gained by the team during the receiver's previous turns (excluding the current one).
*/
public int gamePoints(TeamId t) {
return PackedScore.gamePoints(packed, t);
}
/**
* Gives the number of points made by a given team in the current turn and before.
* @param t the team.
* @return an int the total number of points gained by the team t during the receiver's current game.
*/
public int totalPoints(TeamId t) {
return PackedScore.totalPoints(packed, t);
}
/**
* Computes the {@link Score} of the team after a {@link Trick} has been played.
* @param winningTeam the teamId of the team that won the trick.
* @param trickPoints number of tricks gained by the team during the current turn.
* @throws IllegalArgumentException if trickPoints is negative.
* @return the updated scores.
*/
public Score withAdditionalTrick(TeamId winningTeam, int trickPoints) {
checkArgument(trickPoints>=0);
return new Score(PackedScore.withAdditionalTrick(packed, winningTeam, trickPoints));
}
/**
* Gives the {@link Score} at the beginning of the next turn.
* @return the updated scores for the next turn
*/
public Score nextTurn() {
return new Score(PackedScore.nextTurn(packed));
}
@Override
public boolean equals(Object that0) {
return that0 != null
&& that0.getClass() == this.getClass()
&& this.packed() == ((Score)that0).packed();
}
@Override
public int hashCode() {
return Long.hashCode(packed);
}
@Override
public String toString() {
return PackedScore.toString(packed);
}
}

32
jass/TeamId.java Normal file
View File

@ -0,0 +1,32 @@
package ch.epfl.javass.jass;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @author Charles BEAUVILLE
* @author Celia HOUSSIAUX
*
*/
public enum TeamId {
TEAM_1,
TEAM_2;
/**
* The number of teams.
*/
public static final int COUNT = 2;
/**
* The list of all teams.
*/
public static final List<TeamId> ALL = Collections.unmodifiableList(Arrays.asList(values()));
/**
* Gives the other team.
* @return a TeamId the opposing team.
*/
public TeamId other() {
return this.equals(TEAM_1) ? TEAM_2 : TEAM_1;
}
}

207
jass/Trick.java Normal file
View File

@ -0,0 +1,207 @@
package ch.epfl.javass.jass;
import static ch.epfl.javass.Preconditions.*;
import ch.epfl.javass.jass.Card.Color;
/**
* The representation of a trick of a jass game.
* @author Charles BEAUVILLE
* @author Celia HOUSSIAUX
*
*/
public final class Trick {
/**
* The trick representing an invalid trick that is the representation of an empty trick.
*/
public final static Trick INVALID = new Trick(PackedTrick.INVALID);
/**
* This method gives a brand new empty trick associated with the first player.
* @param trump
* @param firstPlayer
* @return a new empty trick.
*/
public static Trick firstEmpty(Color trump, PlayerId firstPlayer) {
return new Trick(PackedTrick.firstEmpty(trump, firstPlayer));
}
/**
* This method gives the trick based on it packed version.
* @param packed
* @throws IllegalArgumentException if packed is not valid
* @return a the trick of the packed version.
*/
public static Trick ofPacked(int packed) {
checkArgument(PackedTrick.isValid(packed));
return new Trick(packed);
}
private final int packed;
private Trick(int packed) {
this.packed = packed;
}
/**
* This method gives the packed version of a trick.
* @return packed.
*/
public int packed() {
return packed;
}
/**
* The empty trick that follows (this). It means the trick empty has the same trump color, the following index of (this)
and the first player winner of (this).
* @throws IllegalStateException if packed is the last trick.
* @return an empty trick that follows (this).
*/
public Trick nextEmpty() {
if (!isFull())
throw new IllegalStateException();
return new Trick(PackedTrick.nextEmpty(packed));
}
/**
* Checks if the trick is empty meaning no card have been played.
* @return true if (this) is empty.
*/
public boolean isEmpty() {
return PackedTrick.isEmpty(packed);
}
/**
* Checks if the trick is full meaning all cards have been played.
* @return true if (this) is full.
*/
public boolean isFull() {
return PackedTrick.isFull(packed);
}
/**
* Checks if the current trick is the last one of the turn
* @return true if (this) is the last trick.
*/
public boolean isLast() {
return PackedTrick.isLast(packed);
}
/**
* Computes the size of the trick i.e the number of cards that have been played.
* @return an int the size of the trick.
*/
public int size() {
return PackedTrick.size(packed);
}
/**
* Gives the trump color of the trick.
* @return a Color the trump color of the trick.
*/
public Color trump() {
return PackedTrick.trump(packed);
}
/**
* Computes the index of the trick in the current turn.
* @return an int the index of the trick.
*/
public int index() {
return PackedTrick.index(packed);
}
/**
* This method gives the player at the given index.
* @param index an int.
* @throws IndexOutOfBoundsException if the index is invalid.
* @return the player of the trick at the given index.
*/
public PlayerId player(int index) {
return PackedTrick.player(packed, checkIndex(index, PlayerId.COUNT));
}
/**
* Gives the card of the trick at the given index.
* @param index an int.
* @return the card of the trick at the given index.
*/
public Card card(int index) {
return Card.ofPacked(PackedTrick.card(packed, checkIndex(index, size())));
}
/**
* This method gives a trick identical as (this) plus the card c.
* @param c a card to be added to the trick.
* @throws IllegalStateException if packed is full.
* @return (this) plus the card c.
*/
public Trick withAddedCard(Card c) {
if (isFull())
throw new IllegalStateException();
return new Trick(PackedTrick.withAddedCard(packed, c.packed()));
}
/**
* This method gives the original color of the trick (the color of the first card played).
* @throws IllegalStateException if packed is empty.
* @return the color of the first card played.
*/
public Color baseColor() {
if (isEmpty())
throw new IllegalStateException();
return PackedTrick.baseColor(packed);
}
/**
* This method gives a subset of hand representing all the cards that can be played during the next trick.
* @param hand the cardset to check the playable cards in it.
* @throws IllegalStateException if packed is full.
* @return a subset of hand representing all the cards that can be played by the player during the next trick.
*/
public CardSet playableCards(CardSet hand) {
if (isFull())
throw new IllegalStateException();
return CardSet
.ofPacked(PackedTrick.playableCards(packed, hand.packed()));
}
/**
* Computes the number of points the trick is worth.
* @return an int the number of points the trick is worth.
*/
public int points() {
return PackedTrick.points(packed);
}
/**
* Gives the winning player of the trick
* @return the PlayerId of the player winning the trick.
*/
public PlayerId winningPlayer() {
return PackedTrick.winningPlayer(packed);
}
@Override
public String toString() {
return PackedTrick.toString(packed);
}
@Override
public boolean equals(Object that0) {
return that0 != null && that0.getClass() == this.getClass()
&& this.packed() == ((Trick) that0).packed();
}
@Override
public int hashCode() {
return Integer.hashCode(packed);
}
}

162
jass/TurnState.java Normal file
View File

@ -0,0 +1,162 @@
package ch.epfl.javass.jass;
import ch.epfl.javass.jass.Card.Color;
import static ch.epfl.javass.Preconditions.*;
/**
* The representation of the state of a turn during a game of Jass.
* @author Charles BEAUVILLE
* @author Celia HOUSSIAUX
*
*/
public final class TurnState {
/**
* Creates the initial state of a turn
* @param trump the trump color of the turn
* @param score the score at the beginning of the turn
* @param firstPlayer the first player of the turn
* @return the initial state of the turn
*/
public static TurnState initial(Color trump, Score score, PlayerId firstPlayer) {
long aS = score.packed();
long uC = PackedCardSet.ALL_CARDS;
int aT = PackedTrick.firstEmpty(trump, firstPlayer);
return new TurnState(aS, uC, aT);
}
/**
* Creates a new turnstate given a packed score, a packed set of unplayed cards and a packed trick.
* @param pkScore the packed version of the score the turn will have.
* @param pkUnplayedCards the packed version of the set of cards that have not yet been played during the turn.
* @param pkTrick the packed version of the trick the turn will have
* @throws IllegalArgumentException if pkScore, pkUnplayedCards and pkTrick are not valid.
* @return the new turn state with the new score, set of unplayed cards and trick.
*/
public static TurnState ofPackedComponents(long pkScore, long pkUnplayedCards, int pkTrick) {
checkArgument(PackedScore.isValid(pkScore) && PackedCardSet.isValid(pkUnplayedCards) && PackedTrick.isValid(pkTrick));
return new TurnState(pkScore, pkUnplayedCards, pkTrick);
}
private final long currentScore;
private final long unplayedCards;
private final int currentTrick;
private TurnState(long aS, long uC, int aT){
currentScore = aS;
unplayedCards = uC;
currentTrick = aT;
}
/**
* Gets the packed version of the current score of the turn.
* @return a long the packed version of the score of the turn.
*/
public long packedScore() {
return currentScore;
}
/**
* Gets the packed version of the current unplayed cards of the turn.
* @return a long the packed version of the set of unplayed cards of the turn.
*/
public long packedUnplayedCards() {
return unplayedCards;
}
/**
* Gets the packed version of the current trick of the turn.
* @return a long the packed version of the trick of the turn.
*/
public int packedTrick() {
return currentTrick;
}
/**
* Gets the current score of the turn.
* @return the score of the turn.
*/
public Score score() {
return Score.ofPacked(currentScore);
}
/**
* Gets the current CardSet of unplayed cards of the turn.
* @return the CardSet of unplayed cards of the turn.
*/
public CardSet unplayedCards() {
return CardSet.ofPacked(unplayedCards);
}
/**
* Gets the current trick of the turn.
* @return the trick of the turn.
*/
public Trick trick() {
return Trick.ofPacked(currentTrick);
}
/**
* Checks if the trick is the last one before the end of the turn.
* @return a boolean if the trick is the last one.
*/
public boolean isTerminal() {
return currentTrick == PackedTrick.INVALID;
}
/**
* Get the next player that will play in the turn.
* @throws IllegalStateException if the current trick is full.
* @return the PlayerId of the next player of the trick.
*/
public PlayerId nextPlayer() {
if(trick().isFull())
throw new IllegalStateException();
return PackedTrick.player(currentTrick, PackedTrick.size(currentTrick));
}
/**
* Compute the new turnState when a given card has been played
* @param card the card to be played
* @throws IllegalStateException if the trick is full.
* @return the new turn state with the card played
*/
public TurnState withNewCardPlayed(Card card) {
if(trick().isFull() && unplayedCards().contains(card))
throw new IllegalStateException();
return ofPackedComponents(currentScore, PackedCardSet.remove(unplayedCards, card.packed()), PackedTrick.withAddedCard(currentTrick, card.packed()));
}
/**
* Computes a new turn state after the trick has been collected, with updated score and a new trick.
* @throws IllegalStateException the trick is not full.
* @return the new turn state with the next trick and the updated score
*/
public TurnState withTrickCollected() {
if(!trick().isFull())
throw new IllegalStateException();
return new TurnState(
PackedScore.withAdditionalTrick(currentScore, PackedTrick.winningPlayer(currentTrick).team(), PackedTrick.points(currentTrick)),
unplayedCards,
PackedTrick.nextEmpty(currentTrick));
}
/**
* Computes a new turn state with a new card and collects the trick if it is full
* @param card the card to be played
* @return the new turn state when the given card is played or the trick is full
*/
public TurnState withNewCardPlayedAndTrickCollected(Card card) {
TurnState t = withNewCardPlayed(card);
if(t.trick().isFull())
t = t.withTrickCollected();
return t;
}
}