From c24b9f3d28cb11d692a8f34f2f541ba814215dd6 Mon Sep 17 00:00:00 2001 From: charlesbvll Date: Mon, 30 Sep 2019 12:55:33 +0200 Subject: [PATCH] Add files via upload --- jass/CardSet.java | 174 ++++++++++++++++++++ jass/Jass.java | 30 ++++ jass/JassGame.java | 188 +++++++++++++++++++++ jass/MctsPlayer.java | 213 ++++++++++++++++++++++++ jass/PacedPlayer.java | 80 +++++++++ jass/PackedCard.java | 165 +++++++++++++++++++ jass/PackedCardSet.java | 236 ++++++++++++++++++++++++++ jass/PackedScore.java | 206 +++++++++++++++++++++++ jass/PackedTrick.java | 357 ++++++++++++++++++++++++++++++++++++++++ jass/Player.java | 67 ++++++++ jass/PlayerId.java | 37 +++++ jass/Score.java | 114 +++++++++++++ jass/TeamId.java | 32 ++++ jass/Trick.java | 207 +++++++++++++++++++++++ jass/TurnState.java | 162 ++++++++++++++++++ 15 files changed, 2268 insertions(+) create mode 100644 jass/CardSet.java create mode 100644 jass/Jass.java create mode 100644 jass/JassGame.java create mode 100644 jass/MctsPlayer.java create mode 100644 jass/PacedPlayer.java create mode 100644 jass/PackedCard.java create mode 100644 jass/PackedCardSet.java create mode 100644 jass/PackedScore.java create mode 100644 jass/PackedTrick.java create mode 100644 jass/Player.java create mode 100644 jass/PlayerId.java create mode 100644 jass/Score.java create mode 100644 jass/TeamId.java create mode 100644 jass/Trick.java create mode 100644 jass/TurnState.java diff --git a/jass/CardSet.java b/jass/CardSet.java new file mode 100644 index 0000000..4996b02 --- /dev/null +++ b/jass/CardSet.java @@ -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 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); + } +} diff --git a/jass/Jass.java b/jass/Jass.java new file mode 100644 index 0000000..29c882e --- /dev/null +++ b/jass/Jass.java @@ -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; +} diff --git a/jass/JassGame.java b/jass/JassGame.java new file mode 100644 index 0000000..f070da8 --- /dev/null +++ b/jass/JassGame.java @@ -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 players; + private final Map playerNames; + private final Random shuffleRng; + private final Random trumpRng; + + private final Map hands = new HashMap<>(); + private final List 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 players, + Map 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 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); + } + } +} diff --git a/jass/MctsPlayer.java b/jass/MctsPlayer.java new file mode 100644 index 0000000..207dd31 --- /dev/null +++ b/jass/MctsPlayer.java @@ -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 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 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 addNode(long hand) { + ArrayList 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++; + } + + } +} diff --git a/jass/PacedPlayer.java b/jass/PacedPlayer.java new file mode 100644 index 0000000..a6529e3 --- /dev/null +++ b/jass/PacedPlayer.java @@ -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 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); + } +} diff --git a/jass/PackedCard.java b/jass/PackedCard.java new file mode 100644 index 0000000..89e5f90 --- /dev/null +++ b/jass/PackedCard.java @@ -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(); + } + +} diff --git a/jass/PackedCardSet.java b/jass/PackedCardSet.java new file mode 100644 index 0000000..5f5f4df --- /dev/null +++ b/jass/PackedCardSet.java @@ -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(); + } +} diff --git a/jass/PackedScore.java b/jass/PackedScore.java new file mode 100644 index 0000000..f323183 --- /dev/null +++ b/jass/PackedScore.java @@ -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)); + } +} diff --git a/jass/PackedTrick.java b/jass/PackedTrick.java new file mode 100644 index 0000000..253336b --- /dev/null +++ b/jass/PackedTrick.java @@ -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); + } + +} diff --git a/jass/Player.java b/jass/Player.java new file mode 100644 index 0000000..9df5059 --- /dev/null +++ b/jass/Player.java @@ -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, MapplayerNames) { + } + + /** + * 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) { + } + + +} diff --git a/jass/PlayerId.java b/jass/PlayerId.java new file mode 100644 index 0000000..0e64ab8 --- /dev/null +++ b/jass/PlayerId.java @@ -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 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; + } + +} diff --git a/jass/Score.java b/jass/Score.java new file mode 100644 index 0000000..8de419a --- /dev/null +++ b/jass/Score.java @@ -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); + } +} diff --git a/jass/TeamId.java b/jass/TeamId.java new file mode 100644 index 0000000..81c0019 --- /dev/null +++ b/jass/TeamId.java @@ -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 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; + } +} diff --git a/jass/Trick.java b/jass/Trick.java new file mode 100644 index 0000000..f209601 --- /dev/null +++ b/jass/Trick.java @@ -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); + } + +} diff --git a/jass/TurnState.java b/jass/TurnState.java new file mode 100644 index 0000000..b55f960 --- /dev/null +++ b/jass/TurnState.java @@ -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; + } +}