2

Note: I am aware of the poor wording of the title but I was unable to come up with a better one. If anyone has a better idea, then please edit the title.

I'm trying to recreate a card game but kind of struggling with a problem. Every card in the game has a color (enum) and rank (enum). Some of the cards have some special abilities which require additional data. These cards always have a special color only given to cards with special abilities. The Game has a stack to which played cards are added through a method void playCard(Card).

An example special ability is changing the color of the card on the top of the stack. The new color is totally up to the player to decide. The problem is that this new color needs to be decided when the card is played and before that the special card's color is just SPECIAL. But how do I pass the new color data in a nice way (i.e. so I don't need to use instanceof and extract the data but just get it as card.getColor())? Ideally I'd like it to be a special card before and after playing it to disguise as a normal card (or make it seem like to). If possible I'd still want to be able to retrieve the original, special card.

I thought about solving this by creating a FakeCard and adding a method to SpecialCard to generate the FakeCard and pass the required data to that method but I'm afraid of additional coupling that would occur as different special cards require different additional data and don't want to violate Open/Closed principle (using this approach would require me to). I can't think of anything else though. Do you have any suggestions as to how approach this problem?

  • Is it possible you are overthinking this? When uno card of color especial is played, you need to enter a routine where a non-special color is chosen by the user. Why not simply do that within the getColor() method of the special card class? – JimmyJames Aug 8 '17 at 21:15
  • @JimmyJames basically my current system relies on the card's color to determine whether the card can be placed at the given time. Before the special card is played what would that color be? Also, I'd be nice if after placing the card I still could know the original card as I wouldn't want to limit myself in the future to the special cards always having a special color – Mibac Aug 8 '17 at 21:19
  • I'm trying to understand the scenario: does a "special" card become a "regular" card once the player plays it and decides on a color? If so, do you need to remember which "special" card it was before fixing a color? (and why?) – Andres F. Aug 8 '17 at 21:59
  • @AndresF. Kind of. Say, I have a special card that changes the color. Before I place it, it's color is SPECIAL but after I place it I'd like it to change color to which the player wants. I've done it this way because of the mechanics of this game (you can play a card if it's color matches with the one on top of the stack). It just plays nicely this way without any additional methods for special cards. I need to remember the original card because the game has a specified amount of each of the cards and briefly one of the mechanics is taking a card. – Mibac Aug 8 '17 at 22:09
  • If the pile of cards to take is empty I basically swap (and shuffle) the stack with played cards with the stack of to-take cards. This way I can be sure that the cards in circulation always match with the game's deck of cards (i.e. the amount of every cards is always the same throughout the game) – Mibac Aug 8 '17 at 22:12
2

I'm no java developer, but a long-time Magic The Gathering player, so I understand the fact that some games can have cards that do many and crazy different things.

Did you think about changing the way you represent the relations beween the game, its card stack and the cards? Currently I think you have a game that has a deck of cards and a stack of cards. And you have in the class Game a playCard method that plays a Card, because you think the Game or (a player) plays a card.

But instead of the Game playing the cards, think of the cards changing the game. What if you have a Card base class that your cards implement, and you have a Card::play method that takes a GameState and its card stack as an argument? This way each card has the freedom to manipulate and change the game state. Your cards can add themselves to the stack, and decide their own effects. If the card is not special, it will just be played normally.

Now the color the user wants to give to the special card has to be passed somewhere, so you'd need to subclass Card with SpecialCard so that you can pass that additional argument. You would add the SpecialCard::play(GameState game, Color color) method (which would be unittest-friendly because of the dependency injection).

You would then be able to either call Card::play(GameState game) which would call SpecialCard::play(GameState game) where you would ask the user the color he/she wants to use or you could call in your tests.

For a special card, once the user input being retrieved, SpecialCard::play(GameState game) would call SpecialCard::play(GameState game, Color color) with the right information.

Sharing the same internals avoids code duplication and is good for testing too.

  • 1
    Thanks a lot! Your answer really helped me and it now works just as I want it to :) – Mibac Feb 7 '18 at 16:56
  • 1
    Happy it was useful. I learned that after I read a paper about anti-objects for an AI challenge. In the paper they show how to implement a game of pacman with a good AI for the ghosts. The result was reversing the way of thinking: put the intelligence in the tiles, not on the independant ghosts. So when you feel blocked, try reversing the problem :). – liberforce Feb 7 '18 at 17:50
0

If the special ability can be "anything" and may require arbitrary input from the player when it is played, then each kind of SpecialCard actually needs to have its own UI code to get that input from the player. So give SpecialCard an invokeAbility() method which is called by playCard() and either have subclasses of SpecialCard (or give it a field SpecialAbility) to implement the different abilities.

  • How exactly should I get the input from the player? Cards can change owners and aren't created by players themselves, rather games create them at the beginning of each game. I'm not sure how can I pass that input – Mibac Aug 8 '17 at 21:45
  • @Mibac: as I wrote: when the game executes playCard(Card)it would call the card's invokeAbility() method which then needs to somehow show a UI to get input from the player. The point is that the player input is specific to that ability, so the UI to get that input belongs with that ability. – Michael Borgwardt Aug 10 '17 at 9:11
0

It seems like a problem that can be solved using polymorphism/inheritance. I haven't done Java in a bit and my syntax probably isn't valid but I think I can get my point across. I'm not sure what is preventing you from doing the following

// All classes that implement ICard is guaranteed to have the following private fields and public methods.
interface ICard {
   private Color color;
   private Rank rank;

   public Color getColor();
   public Rank getRank();
   public void invokeAbility();
}

public class Card implements ICard {
   public Card(Color color){
      this.color = color;
   }
   // ... implement getColor and getRank and invokeAbility
}

// SpecialCard is an instance of Card so it can be used in the same manner as Card
public class SpecialCard extends Card {

   // overload the constructor
   public SpecialCard(){

     // Special card instantiated w/o Color enum is defaulted to Color.SPECIAL
     super(Color.SPECIAL);
   }

   public SpecialCard(Color color){

     super(color);
   }

   // Override invokeAbility
   public invokeAbility(){
     // ....
   }
}
  • The invokeAbility may require input from the user. How would I get it? Cards can change owners and aren't created by players themselves, rather games create them at the beginning of each game which limits the possibilities – Mibac Aug 8 '17 at 21:57
  • 1
    I would define class Game which that initializes a new game by creating instances of class Player, that each get instantiated w/ a List of class Card. You can set this.currentPlayer to be equal whose turn it currently is. Then you can add a method to class Game like game.submitMove(Move move) that will call method this.currentPlayer.doMove(Move move) which will make player call card.invokeAbility. To get input from the user you need to start a loop, like adding in public static void main a while loop to get user input, call method on game while (!game.isOver) – James Choi Aug 8 '17 at 22:28
  • Simply put, some kind of event loop needs to be started somewhere that won't end until user quits the game. Each iteration should wait for a input, and perhaps use that input to call a method (such as invokeAbility) and then repeat the process until exit. However you choose to organize your classes is irrelevant. – James Choi Aug 8 '17 at 22:37
  • I know how to get the input. The thing I don't know though is how to pass it to the card. – Mibac Aug 8 '17 at 22:39
  • Pass it to class Game instance method, which will call method on player which will manage the cards that it owns – James Choi Aug 8 '17 at 22:40
0

With what I understand from the question I would create a ColorChangingCard' class that extends the baseCard` with some the methods that make it special. These methods call methods on the parent class to change the state of the card.

0

This is rough and the details of whether you want to have a SPECIAL color or not can be changed but it should give you the basic idea:

public interface Card
{
    public enum Color {
        YELLOW, RED, GREEN, BLUE, SPECIAL
    }

    boolean canPlaceOn(Card other);

    Color color();

    default Color playedColor()
    {
        return color();
    }

    Rank rank();

    public void assign(Player player);

    Player player();
}

public class BasicCard extends AbstractCard
{
    private final Rank rank;
    private final Color color;

    BasicCard(Rank rank, Color color)
    {
        this.rank = rank;
        this.color = color;
    }

    @Override
    public boolean canPlaceOn(Card other)
    {
        return other.color() == this.color() || this.rank() == other.rank();
    }

    @Override
    public Color color()
    {
        return color;
    }

    @Override
    public Rank rank()
    {
        return rank;
    }
}

public class SpecialCard extends AbstractCard
{
    private Color color = Color.SPECIAL;

    @Override
    public boolean canPlaceOn(Card other)
    {
        // modify as needed
        return true;
    }

    @Override
    public Color Color()
    {
       return Color.SPECIAL;
    }

    @Override
    public Color playedColor()
    {
       while (color == Color.SPECIAL) {
           color = UserInterface.selectColor(player());
       }

       return color;
    }

    @Override
    public Rank rank()
    {
        // not sure what goes here
        return null;
    }
}
  • But how would I then show the his cards? The UI code needs to know the card's color but given this approach, calling SpecialCard's color method would require the player to choose the color immediately. I don't want that - I want the player to decide the color when playing the card. – Mibac Aug 9 '17 at 15:26
  • Create a method, playedColor() in the Card interface. I'll update the example. – JimmyJames Aug 9 '17 at 15:36
0
 > Class A requires additional info when using it. How to pass it to B?

Your original question was: how to tell the PlayEngine "B" that Card "A" is something special?

If you follow the tell, don-t ask pattern you learn that player "X" tells specialCard "A" which color to use and specialCard "A" tells PlayEngine "B" which color to use.

You need two different types of card:

  • most cards are of type "Card where color cannot be changed"
  • and there are a few "Card where the player can tell the card which color it should have at runtime"

Both card types have a method play() that tells the PlayEngine which color is next

If your StandardCard also has a setColor() method (which i do not recomment) then it-s implementation should throw a PlayerIsCheetingException :-) )

0

Try a visitor with 2 methods with the same name, but one taking RegularCard as a parameter and one with SpecialCard.

Then you can define a method in BaseCard which will take visitor and invoke with itself resolving the subtype.

Something like this:

class CardDealer {
    void play(RegularCard card) {
        ...
    }
    void play(SpecialCard specialCard) {
        ...
    }
}
abstract class Card {
    draw(CardDealer visitor) {
        visitor.play(this);
    }
}

or

interface Playable {
    void play(CardDealer dealer);
}

and overwrite method accordingly, in this case you can use different names for visitor methods

0

Your problem in modeling comes from your getColor() being dual-use:

  1. Determining whether you can play the card, meaning following suit. bool canPlay(Suit) would be more appropriate.
  2. Determining which suit has to be followed by the next card. Suit getSuit() would be appropriate.

So, you need two functions, one returning its played color, Suit getSuit(), and one determining whether it can be played canPlay(Suit).

Special Cards additionally need a function to change the color, setSuit().

Depending on how you decide to play it, you need at least two classes, RegularCard and SpecialCard. Either they implement / inherit from a common interface / base, or the latter extends the former. Both solutions are valid.

0

The card doesn't change color. The color named when the card is played is part of the game state, along with, for example, the turn direction (if you implement a "reverse" card).

You might find it easier to implement the game in a test-driven fashion, instead of trying to come up with an initial class design. The input to each test would be the game state and a possible card to play, and the output would be whether it is legal to play that card and the resulting game state. Then you can test and implement one rule at a time. Factor out classes as it becomes convenient.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.