
com.github.markozajc.juno.hands.impl.StrategicUnoHand Maven / Gradle / Ivy
package com.github.markozajc.juno.hands.impl;
import java.util.List;
import java.util.Map.Entry;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.github.markozajc.juno.cards.UnoCard;
import com.github.markozajc.juno.cards.UnoCardColor;
import com.github.markozajc.juno.cards.impl.UnoActionCard;
import com.github.markozajc.juno.cards.impl.UnoDrawCard;
import com.github.markozajc.juno.cards.impl.UnoNumericCard;
import com.github.markozajc.juno.cards.impl.UnoWildCard;
import com.github.markozajc.juno.game.UnoGame;
import com.github.markozajc.juno.hands.UnoHand;
import com.github.markozajc.juno.utils.UnoUtils;
/**
* An automated hand that uses actual strategic "thinking" to decide cards and colors to return.
* Is suitable for production so you may use it as a "CPU" opponent in your code.
*
* @author Marko Zajc
*/
public class StrategicUnoHand extends UnoHand {
/**
* Creates a new {@link StrategicUnoHand}.
*
* @param name hand's name
*/
public StrategicUnoHand(@Nonnull String name) {
super(name);
}
private static final int DRAW_CARD_THRESHOLD = 3;
@Nullable
private static UnoDrawCard chooseDrawCard(List possiblePlacements, List> colorAnalysis, UnoGame game, UnoCard topCard) {
if (topCard instanceof UnoDrawCard && !((UnoDrawCard) topCard).isPlayed()) {
// In case the other hand placed a draw card
if (!((UnoDrawCard) topCard).getOriginalColor().equals(UnoCardColor.WILD)) {
// If the card is NOT a draw four card (or any other custom wild draw card)
UnoDrawCard result = chooseBestCard(possiblePlacements, colorAnalysis, UnoDrawCard.class);
if (result != null)
return result;
} else {
return UnoUtils.filterKind(UnoDrawCard.class, possiblePlacements).get(0);
}
}
// Defends self if "attacked" with a Draw X card
// Places an action card if possible (both action cards do the same thing in 2 player
// games so yeah)
if (game.playerOneHand.getSize() <= DRAW_CARD_THRESHOLD) {
List drawCards = UnoUtils.filterKind(UnoDrawCard.class, possiblePlacements);
if (!drawCards.isEmpty())
return drawCards.get(0);
}
// Places a Draw X card in case opponent's hand size is less or equal to
// DRAW_CARD_THRESHOLD
return null;
}
@Nullable
private static UnoActionCard chooseActionCard(List possiblePlacements, List> colorAnalysis) {
return chooseBestCard(possiblePlacements, colorAnalysis, UnoActionCard.class);
}
@Nullable
private static UnoNumericCard chooseNumericCard(List possiblePlacements, List> colorAnalysis) {
return chooseBestCard(possiblePlacements, colorAnalysis, UnoNumericCard.class);
}
/**
* Does some strategic thinking; uses the analysis of this hand's cards and tries to
* place the card that has the color of most cards. If, for example, the hand has two
* blue and one red card, this will prefer the blue cards.
*
* @param
* type of the {@link UnoCard} to return
* @param possiblePlacements
* all possible cards
* @param colorAnalysis
* color analysis of the entire hand
* ({@link UnoUtils#analyzeColors(List)})
* @param cardType
* @return the best possible card or {@code null} if there are no cards of the
* requested kind
*/
@Nullable
private static T chooseBestCard(List possiblePlacements, List> colorAnalysis, Class cardType) {
List filteredPossiblePlacements = UnoUtils.filterKind(cardType, possiblePlacements);
if (filteredPossiblePlacements.isEmpty())
return null;
// In case there's no card of the requested kind
for (Entry color : colorAnalysis) {
if (color.getValue().equals(UnoCardColor.WILD))
continue;
// Skips the wild cards because it might be a good idea to save them for later
List matches = UnoUtils.getColorCards(color.getValue(), filteredPossiblePlacements);
// Gets the cards of
if (!matches.isEmpty())
return matches.get(0);
}
return filteredPossiblePlacements.get(0);
// Fallback method
}
@Override
public UnoCard playCard(UnoGame game, boolean drawn) {
UnoCard top = game.discard.getTop();
List possible = UnoUtils.analyzePossibleCards(top, this.cards);
if (possible.isEmpty())
return null;
// Draws a card if no other option is possible
List> colorAnalysis = UnoUtils.analyzeColors(getCards());
// Analyzes the colors
UnoDrawCard drawCard = chooseDrawCard(possible, colorAnalysis, game, top);
if (drawCard != null)
return drawCard;
// Places an action card if possible
UnoActionCard actionCard = chooseActionCard(possible, colorAnalysis);
if (actionCard != null)
return actionCard;
// Places an action card if possible
UnoNumericCard numericCard = chooseNumericCard(possible, colorAnalysis);
if (numericCard != null)
return numericCard;
// Places a numeric card if possible
UnoWildCard wild = UnoUtils.filterKind(UnoWildCard.class, possible).stream().findFirst().orElse(null);
if (wild != null)
return wild;
// places a Wild card if available
return possible.get(0);
// Places the first possible card if everything else fails
}
@SuppressWarnings("null")
@Override
public UnoCardColor chooseColor(UnoGame game) {
return UnoUtils.analyzeColors(this.cards)
.stream()
.filter(p -> !p.getValue().equals(UnoCardColor.WILD))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Couldn't choose a color (UnoUtils malfuntioned!)"))
.getValue();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy