Re: Design decision for a game
On Tue, 20 May 2008, pek wrote:
Say you have a class that holds a set of cards. When a card is added it
has to obey certain rules. For example, if it is full and no other cards
can be added, an exception should be thrown. If the card is not the
correct suite, again, an exception should be thrown etc.
Tiny comment: the word is 'suit', like a set of clothes, not 'suite'. I'm
guessing english is not your first language - although this is the only
error i noticed in your post!
Now, depending on the options of the game, some rules apply and some
don't. So clearly, the rules aren't inside the class itself, but they
are added by the engine using an addRule method. So I thought that I
could create an interface called Rule with a method called addCard. The
class holds a list of rules and every time a card is added it calls them
in the added order.
Okay. Firstly, you have two different kinds of rules here. The first kind
is a permissibility rule: it says whether a card can be added to a house
or not. The second kind is an action rule: it says that when a certain
situation comes to pass, something should happen.
I wouldn't try and handle both kinds of rule within one framework myself.
I'd deal with permissibility and action rules separately.
If your only permissibility rules are about matching suit and not
exceeding 31 points, i don't think i'd reify these. I'd just write two
guard clauses at the top of House.addCard to check for those conditions
and throw an exception if they apply. If there are further permissibility
rules, or if they can change (say between variants of the game), i'd
reify.
(Incidentally, are people familiar with the term 'reify'? It's an old OO
term, but one that seems not to be used much these days - it simply means
to make something into an object.)
The second kind of rule, i would reify, since there are quite a few of
them.
But, in order for this to work, I found two problems: A) the class that
implements the Rule has to have a lot of information (the list of cards,
if it is already closed etc.). How do I work around this? Should the
method of the interface have a list of the needed information in
parameters? Should the class pass itself in all rules and provide
getters for all attributes?
That's what i'd do:
public interface Rule {
public int apply(House house) ;
}
Specifically, i wouldn't have rules apply to cards, but to houses. When
you add a card, you're not going to each rule and asking it what it thinks
of that card, you're really saying "here's the state of the house - is
there anything you want to do?". A rule could then use getters on the
House to examine it, and mutators to make any changes; it would return the
number of points awarded. House then looks like:
public class House {
public static final int MAX_POINTS = 31 ;
private Set<Card> cards ;
private List<Rule> rules ; // shared with other houses
private Suit suit ;
private int points ;
public int addCard(Card card) throws CardException {
if (points > MAX_POINTS) throw new HouseClosedException(this) ;
if (suit != null)
if (card.suit() != suit) throw new WrongSuitException(card, this) ;
else suit = card.suit() ;
cards.add(card) ;
points += card.points() ;
int score = 0 ;
for (Rule rule : rules)
score += rule.apply(this) ;
return score ;
}
public int getPoints() {
return points ;
}
public int numCards() {
return cards.size() ;
}
public Suit getSuit() {
return suit ;
}
public boolean hasCard(Card card) {
return cards.contains(card) ;
}
public void empty() {
cards.clear() ;
suit = null ;
points = 0 ;
}
}
And the rules:
public class ThirtyOnePointRule implements Rule {
public int apply(House house) {
if (house.getPoints() == House.MAX_POINTS) {
house.empty() ;
return 20 ;
}
return 0 ;
}
}
public class SixCardRule implements Rule {
public int apply(House house) {
if (house.numCards() == 6) {
house.empty() ;
return 50 ;
}
return 0 ;
}
}
public class JokerRule implements Rule {
public int apply(House house) {
if (house.hasCard(house.getSuit().JOKER)) {
house.empty() ;
return 50 ;
}
return 0 ;
}
}
I leave Card and Suit to your imagination. They aren't complicated.
B) because different rules have different reactions, I also create a
Listener for that class. Every time something important happens, the
registered listeners get informed. But I have no idea how the class that
implements the Rule can inform the other class to fire an event to the
registered listeners.
I'm not sure that a listener mechanism is the right way to do this. Who
are the listeners? Is it just the GUI? Can rules trigger other rules?
If there's only one listener, then a listener mechanism is probably
overkill. Instead, if you need to present a list of which rules have
applied, i'd modify House.addCard so that it builds up a List<Rule> of
rules which were applied, and returns those to the caller, which will be
the GUI.
In fact, i'd refactor my above design. I'd add a method int score() to
Rule, which gives the number of points that rule is worth, and a String
name(), which gives the rule's description (eg "house reached 31 points").
I'd modify Rule.apply to return a boolean, simply indicating whether that
rule applies. In House.addCard, rather than adding up a score, i'd just
build a List<Rule> of rules which applied (ie returned true from
Rule.apply), and return that instead of a score. In the GUI, or some kind
of controller object, i'd then iterate over the list of rules, printing
out the rule's description and score, and totalling up the score as it
went. This not only separates presentation from game logic, but card
movement from scoring logic!
b) each house is of a particular suite. If a joker of the same suite
(the jokers have suites in this game) is added, any card in the house
are erased and the player wins 50 points in his total score. But if it
doesn't the game is over.
If it doesn't what?
Now, the rule with the six cards fires an event to the listeners that
the rule was satisfied. One of the listeners is the GUI and it reacts
by displaying information. The same thing goes for the 31 points rule
and any rule that others need to know about.
These are events that are fired and listeners react to. But these also
are the rules for the house.
So everything comes down to design decision. Because the rules vary,
and could be expanded, they need to be outside of the House. And
because the events depend on the rules, there has to be a bridge
between them.
I think you should closely examine the idea that you need an event
mechanism. If you can remove that, you remove a lot of complexity.
tom
--
see im down wid yo sci fi crew