/**
* Copyright (C) 2017 Jan Schäfer (jansch@users.sourceforge.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jskat.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import org.jskat.util.rule.SkatRuleFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Holds Cards on a hand or in the Skat
*
* FIXME this should be a real {@link Collection}
*/
public class CardList implements Iterable<Card> {
private static final Random RANDOM = new Random();
private static final Logger LOG = LoggerFactory.getLogger(CardList.class);
protected List<Card> cards = new ArrayList<>();
/**
* Constructor
*/
public CardList() {
}
/**
* Constructor with predefined cards
*
* @param newCards
* Predefined cards
*/
public CardList(final List<Card> newCards) {
cards.addAll(newCards);
}
/**
* Constructor with predefined cards
*
* @param newCards
* Predefined cards
*/
public CardList(final CardList newCards) {
cards.addAll(newCards.cards);
}
public CardList(Card... cards) {
this(Arrays.asList(cards));
}
/**
* Gets a copy of a card list that is immutable
*
* @return Immutable copy of a card list
*/
public CardList getImmutableCopy() {
return new CardList(Collections.unmodifiableList(cards));
}
/**
* Removes a card
*
* @param card
* Card
* @return TRUE, if card was removed successfully
*/
public boolean remove(final Card card) {
return cards.remove(card);
}
/**
* Adds a card
*
* @param card
* Card
* @return TRUE, if card was added successfully
*/
public boolean add(final Card card) {
return cards.add(card);
}
/**
* Adds cards
*
* @param newCards
* Cards to add
* @return TRUE, if cards were added successfully
*/
public boolean addAll(final CardList newCards) {
return addAll(newCards.cards);
}
/**
* Adds cards
*
* @param newCards
* Cards to add
* @return TRUE, if cards were added successfully
*/
public boolean addAll(final Collection<Card> newCards) {
return cards.addAll(newCards);
}
/**
* Gets the size of the hand
*
* @return Size of the hand
*/
public int size() {
return cards.size();
}
/**
* Checks, whether the hand is empty
*
* @return TRUE, if the hand is empty
*/
public boolean isEmpty() {
return cards.isEmpty();
}
/**
* Gets an iterator
*
* @return Iterator
*/
@Override
public Iterator<Card> iterator() {
return cards.iterator();
}
/**
* Checks, whether the hand contains a card
*
* @param card
* Card to check
* @return TRUE, if the hand contains the card
*/
public boolean contains(final Card card) {
return cards.contains(card);
}
/**
* Removes a card.
*
* @param index
* Index of card
* @return Card
*/
public Card remove(final int index) {
return cards.remove(index);
}
/**
* Removes all cards
*
* @param cardsToRemove
* Cards to remove
* @return TRUE, if at least one card was removed
*/
public boolean removeAll(final Collection<Card> cardsToRemove) {
return cards.removeAll(cardsToRemove);
}
/**
* Removes all cards
*
* @param cardsToRemove
* Cards to remove
* @return TRUE, if at least one card was removed
*/
public boolean removeAll(final CardList cardsToRemove) {
return removeAll(cardsToRemove.cards);
}
/**
* Clears the hand
*/
public void clear() {
cards.clear();
}
/**
* Gets the index of a card on the hand
*
* @param card
* Card
* @return Index of card on the hand
*/
public int indexOf(final Card card) {
return cards.indexOf(card);
}
/**
* Tests whether a card with a suit is in the CardList or not
*
* @param gameType
* Game type of the game played
* @param suit
* Suit color
* @return TRUE, when a card with the suit is found
*/
public boolean hasSuit(final GameType gameType, final Suit suit) {
return SkatRuleFactory.getSkatRules(gameType).hasSuit(gameType, this,
suit);
}
/**
* Tests whether a trump card is in the CardList or not
*
* @param gameType
* Game type of the game played
* @return TRUE, when a trump card was found in the CardList
*/
public boolean hasTrump(final GameType gameType) {
for (Card card : cards) {
if (card.isTrump(gameType)) {
return true;
}
}
return false;
}
/**
* Checks whether a jack of a given suit is in the CardList
*
* @param suit
* Suit to check
* @return TRUE if the jack of the tested suit is in the CardList
*/
public boolean hasJack(final Suit suit) {
return cards.contains(Card.getCard(suit, Rank.JACK));
}
/**
* Sets a card on the hand
*
* @param index
* Index on the hand
* @param card
* Card
*/
public void set(final int index, final Card card) {
cards.set(index, card);
}
/**
* Overrides the standard get method (@see {@link List#get(int)}) to handle
* the "not available" index value of -1, so that in this case the method
* returns null
*
* @param index
* Index on the hand
* @return Card
*/
// FIXME (jansch 02.05.2012) seems wrong to me
// Exceptions are used to control program execution
public Card get(final int index) {
try {
return cards.get(index);
} catch (IndexOutOfBoundsException e) {
if (index == -1) {
return null;
}
throw e;
}
}
/**
* Gets the index of a card in the CardList
*
* @param card
* Card to be searched
* @return Index of the Card in the CardList
*/
public int getIndexOf(final Card card) {
int index = -1;
if (cards.contains(card)) {
int currIndex = 0;
while (index == -1 && currIndex < cards.size()) {
if (get(currIndex).getSuit() == card.getSuit()
&& get(currIndex).getRank() == card.getRank()) {
index = currIndex;
}
currIndex++;
}
}
return index;
}
/**
* Gets the suit with the most Cards in the CardList (without considering
* the jacks!)
*
* @return Suit with most Cards in the CardList,<br>
* the highest ranking suit, if there the highest count gives more
* than one suit
*/
public Suit getMostFrequentSuit() {
return getMostFrequentSuit(null);
}
/**
* Gets the suit with the most Cards in the CardList (without considering
* the jacks!), without considering the given suit
*
* @param exclude
* suit to exclude from calculating the most frequent suit
* (normally the trump suit)
* @return Suit with most Cards in the CardList,<br>
* the highest ranking suit, if there the highest count gives more
* than one suit
*/
public Suit getMostFrequentSuit(final Suit exclude) {
int maxCount = 0;
Suit mostFrequentSuitColor = null;
for (Suit suit : Suit.values()) {
if (exclude != null && suit == exclude) {
continue;
}
int cardCount = getSuitCount(suit, false);
if (cardCount > maxCount) {
mostFrequentSuitColor = suit;
maxCount = cardCount;
}
}
return mostFrequentSuitColor;
}
/**
* Returns the number of cards with a given suit dependent on a game type
*
* @param suit
* The suit to search for
* @param countJack
* TRUE if all of the jack should count to the number of suit
* cards (max=8), FALSE otherwise (max=7)
* @return Number of cards with this suit
*/
public int getSuitCount(final Suit suit, final boolean countJack) {
int count = 0;
for (Card card : cards) {
if (card.getSuit() == suit
&& (card.getRank() != Rank.JACK || countJack)) {
count++;
}
}
return count;
}
/**
* Returns the number of potential trump cards for a given suit
*
* @param trumpSuit
* The potential trump suit to search for
* @return Number of trump cards for this suit
*/
public int getTrumpCount(final Suit trumpSuit) {
int count = 0;
for (Card card : cards) {
if (card.getRank() == Rank.JACK || card.getSuit() == trumpSuit) {
count++;
}
}
return count;
}
/**
* Sorts the Cards in the CardList according the sort type SkatConstants
*
* @param gameType
* Game type for sorting
*/
public void sort(final GameType gameType) {
if (gameType == null) {
Collections.sort(cards, new SuitComparator(GameType.CLUBS));
return;
}
if (!containsHiddenCards()) {
switch (gameType) {
case NULL:
Collections.sort(cards, new NullComparator());
break;
case RAMSCH:
Collections.sort(cards, new RamschComparator());
break;
case CLUBS:
case SPADES:
case HEARTS:
case DIAMONDS:
Collections.sort(cards, new SuitComparator(gameType));
break;
case GRAND:
default:
Collections.sort(cards, new SuitComparator(GameType.CLUBS));
break;
}
}
}
private boolean containsHiddenCards() {
boolean result = false;
for (Card card : cards) {
if (null == card) {
result = true;
}
}
return result;
}
/**
* @see Object#toString()
*/
@Override
public String toString() {
StringBuffer output = new StringBuffer();
output.append("{"); //$NON-NLS-1$
Iterator<Card> iter = cards.iterator();
while (iter.hasNext()) {
output.append(iter.next());
if (iter.hasNext()) {
output.append(' ');
}
}
output.append("}"); //$NON-NLS-1$
return output.toString();
}
/**
* Returns the first index of a card with the given suit.
*
* @param suit
* Suit to search
* @param includeJacks
* flag whether to include jacks in the result
* @return First index of a card with the given suit, -1 if there is no such
* card
*/
public int getFirstIndexOfSuit(final Suit suit,
final boolean includeJacks) {
int result = -1;
int index = 0;
for (Card card : cards) {
if (result == -1 && card.getSuit() == suit) {
if (card.getRank() != Rank.JACK || card.getRank() == Rank.JACK
&& includeJacks) {
result = index;
}
}
index++;
}
return result;
}
/**
* Returns the first index of a card with the given suit (including jacks!)
*
* @param suit
* Suit to search
* @return First index of a card with the given suit, -1 if there is no such
* card
*/
public int getFirstIndexOfSuit(final Suit suit) {
return getFirstIndexOfSuit(suit, true);
}
/**
* Returns the last index of a card with the given suit (including jacks!)
*
* @param suit
* Suit to search
* @return Last index of a card with the given suit, -1 if there is no such
* card
*/
public int getLastIndexOfSuit(final Suit suit) {
return getLastIndexOfSuit(suit, true);
}
/**
* Returns the last index of a card with the given suit
*
* @param suit
* Suit to search
* @param includeJacks
* flag whether to include jacks in the result
* @return Last index of a card with the given suit, -1 if there is no such
* card
*/
public int getLastIndexOfSuit(final Suit suit, final boolean includeJacks) {
int result = -1;
int index = 0;
for (Card card : cards) {
if (card.getSuit() == suit) {
if (card.getRank() != Rank.JACK || card.getRank() == Rank.JACK
&& includeJacks) {
result = index;
}
}
index++;
}
return result;
}
/**
* Counts the total points of this CardList
*
* @return the points of the CardList
*/
public int getTotalValue() {
int result = 0;
for (Card c : cards) {
result += c.getPoints();
}
return result;
}
/**
* converts the CardList to an int[4] array for a bitwise representation of
* the CardList, with one int value per suit. The index of the array equals
* the Suit ordinal (0=Clubs, 3=Diamonds).<br>
* <br>
* Using this representation, bitwise operations can be performed on the
* CardList, e.g. by AI players.
*
* @return an int[4] array
*/
public int[] toBinary() {
int[] result = new int[4];
for (Card c : cards) {
result[c.getSuit().ordinal()] += c.toBinaryFlag();
}
return result;
}
/**
* Provides a String view on the binary representation of the CardList for
* logging purposes
*
* @return a loggable String (containing CR/LF chars!)
*/
public String dumpFlag() {
StringBuilder sb = new StringBuilder();
sb.append('\n');
for (int i : toBinary()) {
for (int j = 0; j < 8; j++) {
sb.append((i & (int) Math.pow(2, j)) > 0 ? '1' : '0');
}
sb.append('\n');
}
return sb.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (cards == null ? 0 : cards.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
CardList other = (CardList) obj;
if (cards == null) {
if (other.cards != null) {
return false;
}
} else if (!cards.equals(other.cards)) {
return false;
}
return true;
}
/**
* Returns an unbeatable Grand/suit hand of 10 cards.
*
* @return Unbeatable Grand/suit hand
*/
public final static CardList getPerfectGrandSuitHand() {
return new CardList(Card.CJ, Card.SJ, Card.HJ, Card.DJ, Card.CA,
Card.SA, Card.HA, Card.DA, Card.CT, Card.ST);
}
/**
* Returns random hand.
*
* @param cardCount
* Number of cards
* @return Random hand
*/
public final static CardList getRandomCards(int cardCount) {
CardDeck cardDeck = new CardDeck();
CardList result = new CardList();
for (int i = 0; i < cardCount; i++) {
result.add(cardDeck.remove(RANDOM.nextInt(cardDeck.size())));
}
return result;
}
private class NullComparator implements Comparator<Card> {
@Override
public int compare(Card first, Card second) {
if (first.getSuit().getSuitOrder() < second.getSuit()
.getSuitOrder()) {
return -1;
} else if (first.getSuit().getSuitOrder() > second.getSuit()
.getSuitOrder()) {
return 1;
}
if (first.getNullOrder() < second.getNullOrder()) {
return 1;
} else {
return -1;
}
}
}
private class RamschComparator implements Comparator<Card> {
@Override
public int compare(Card first, Card second) {
// first the jacks
if (first.getRank() == Rank.JACK && second.getRank() == Rank.JACK) {
if (first.getSuit().getSuitOrder() < second.getSuit()
.getSuitOrder()) {
return 1;
} else if (first.getSuit().getSuitOrder() > second.getSuit()
.getSuitOrder()) {
return -1;
}
} else if (first.getRank() == Rank.JACK) {
return -1;
} else if (second.getRank() == Rank.JACK) {
return 1;
}
if (first.getSuit().getSuitOrder() < second.getSuit()
.getSuitOrder()) {
return -1;
} else if (first.getSuit().getSuitOrder() > second.getSuit()
.getSuitOrder()) {
return 1;
}
if (first.getRamschOrder() < second.getRamschOrder()) {
return 1;
} else {
return -1;
}
}
}
private class SuitComparator implements Comparator<Card> {
private final GameType gameType;
public SuitComparator(GameType pGameType) {
gameType = pGameType;
}
@Override
public int compare(Card first, Card second) {
// first the jacks
if (first.getRank() == Rank.JACK && second.getRank() == Rank.JACK) {
if (first.getSuit().getSuitOrder() < second.getSuit()
.getSuitOrder()) {
return 1;
} else if (first.getSuit().getSuitOrder() > second.getSuit()
.getSuitOrder()) {
return -1;
}
} else if (first.getRank() == Rank.JACK) {
return -1;
} else if (second.getRank() == Rank.JACK) {
return 1;
}
// trump cards follow
if (first.getSuit() == gameType.getTrumpSuit()
&& second.getSuit() != gameType.getTrumpSuit()) {
return -1;
} else if (first.getSuit() != gameType.getTrumpSuit()
&& second.getSuit() == gameType.getTrumpSuit()) {
return 1;
} else if (first.getSuit() == gameType.getTrumpSuit()
&& second.getSuit() == gameType.getTrumpSuit()) {
if (first.getSuitGrandOrder() < second.getSuitGrandOrder()) {
return 1;
} else {
return -1;
}
}
// all the other cards
if (first.getSuit().getSuitOrder() < second.getSuit()
.getSuitOrder()) {
return 1;
} else if (first.getSuit().getSuitOrder() > second.getSuit()
.getSuitOrder()) {
return -1;
}
if (first.getSuitGrandOrder() < second.getSuitGrandOrder()) {
return 1;
} else {
return -1;
}
}
}
}