Question

I'm working on a game for iOS where you place buildings on a map each turn, and then at the end of the turn it processes the economy based on the types of buildings placed and the type of terrains that they are placed on.

I'd like to add a deck of event cards to the game. One event card would be drawn from the deck of cards each turn, and it might affect the tiles themselves, the buildings, or the total values of resources that the player has accumulated so far.

As far as the class hierarchy, I have a Game object on top which contains a Tilemap dictionary object which contains the Tile objects. Each Tile object has a Building object, which can be one of a few different classes that inherit from a general Building object.

I quickly ran into a problem where I couldn't have the action of the Building inside the Building object as I originally planned, because there was no way for it to manipulate the Tilemap directly (like placing a certain building adds more Tiles than normal to the map, or reveals whether they have a certain resource). So I had to place a switch statement and the methods for the Building actions inside of the Tilemap.

Now I am playing around with adding the deck of event cards. I wanted to have a CardDeck class inside the Game class, and then the CardDeck class would have the array of card objects inside. The Game would tell the CardDeck to draw a new card and to execute the action of that card.

The problem of course is how do I get the action of the card to affect the Tilemap or the Game object? Due to encapsulation, they don't know anything about each other. Right now the first thought is to have a switch statement and put the methods inside the Game class.. but it seems like this will only allow them to influence the variables of the Game object, or to send messages to the Tilemap to manipulate it. If I do it this way, what is the point of having a different Card object for each type of card anyway?

I know this is a fundamental programming question, but I cannot work out the logic in my brain. I would really appreciate help on this one! Thank you!

Was it helpful?

Solution

There are many ways I can think of to do this, which one is best depends entirely on your specific problem. I'll suggest two:

The first is to jump out of OOP entirely. Object Oriented programming is all about coupling data with functionality, but in this case there really isn't any data to speak of: a Card is just a function applied to your game state. You can make an NSArray of blocks, and pick a function to execute at random.

typedef void (^EventCard)(NSArray* buildings, Map* map);

EventCard earthQuake = [^ void {
    //show an alertview saying its an earthquake so the user knows what is going on

    //apply earthquake to the buildings and map

} copy];

The disadvantage of this is that you probably will end up writing a lot of duplicate code, and won't be able to easily add new event cards.

The next solution is similar to what you have now, but more extensible. Your Card class becomes a set of dictionaries, whose keys correspond to properties of your building and map objects, or are triggers for logic in the applyEventCardMethod. You can (and should) read those dictionaries in from plist files or JSON text which are opened and used to make a instance of Card. So your Card.h looks like this

@interface Card : NSObject

@property (strong) NSDictionary* attributesToAffect;
@property (strong) NSArray* targetFilters;

and the earthquake example might have targetFilters of @[@"all_buildings"] and the attributesToAffect would be @{currHp: @-100}, if your building class has a currHp property. ApplyEventCard now looks like this

-(void) applyEventCard
{
     //pick a random Event card

     //get all targets based on the "targets" key

     //loop through targets
          //loop through the rest of the keys

          NSInteger previousValue = [[currBuilding valueForKey: currKey] integerValue];
          NSInteger valueToApply = eventCardToApply.attributeDict[currAttribute];
                [currBuilding setValue: previousValue - valueToApply forKey: currKey];
}

The advantage of this approach is that it is incredibly easy to add new cards, and there is no code required. However, you are potentially seriously constrained in the effects your cards can have. If a lot of your cards need custom logic of some sort there may not be any way to avoid a monstrous switch statement.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top