tags:

views:

120

answers:

7

How do you code special cases for objects?

For example, let's say I'm coding an rpg - there are N = 5 classes. There are N^2 relationships in a matrix that would determine if character A could attack (or use ability M on) character B (ignoring other factors for now).

How would I code this up in OOP without putting special cases all over the place?

Another way to put it is, I have something maybe

ClassA CharacterA;
ClassB CharacterB;

if ( CharacterA can do things to CharacterB )

I'm not sure what goes inside that if statement, rather it be

if ( CharacterA.CanDo( CharacterB ) )

or a metaclass

if ( Board.CanDo( CharacterA, CharacterB ) )

when the CanDo function should depend on ClassA and ClassB, or attributes/modifiers with ClassA/ClassB?

+1  A: 

What is the definition of "see"? If they occupy the same square? If so, this will be answered in how you implement collision detection (or whatever in this case) rather then OOP relationships between characters. Without knowing more information, I would approach the problem in this manner (in C++/pseudo code for illustration):

class Character {
private:
    matrixSquare placement;
public:
    Character() {};
    ~Character {};
    matrixSquare getLocation() { return placement;};
};

class GameBoard {
private:
    //your 5 x 5 matrix here
public:
    GameBoard() {};
    ~GameBoard() {};
    boolean isOccupied(matrixSquare)
    {
     if (occupied)
     {
      //do something
      return true;
     }
     else
     {
      return false;
     }
    }
};

The trick here is to define the relationship between your character pieces and your implementation of the playing field. After that is established you could then clarify how you determine if two characters are in the same square, adjoining squares, etc... Hope that helps.

Knowles Atchison
Sorry, literally "see" - say a wizard is invisible to a werewolf.
Timmy
In that situation, having special cases is unavoidable. It could be mitigated with a rule set that is stored in a table, hash set, whatever, but when it is all said and done, the program has to reference, somewhere, that class A > class B or has different abilities and powers, etc...
Knowles Atchison
+1  A: 

I would say use design patterns, generally I think Observer, Mediator and Visitor patterns are quite good for managing complex inter-object relationships.

oykuo
+1  A: 

I would (assuming C++) give each class a std::string identifier, returned by a getter method on the class's instance, and use a std::map< std::pair<std::string, std::string>, ... > to encode the special cases of relationship between classes, all nice and ordered in one place. I'd also access that map exclusively through a getter function so that changing the strategy for encoding some or all of the special cases is made easy as pie. E.g.: if only a few pairs of classes out of the 25 have the "invisibility" property, the map in that case might contain only the few exceptions that do have that property (for a boolean property like this a std::set might actually be a preferable implementation, in C+_).

Some OO languages have multi-dispatch (Common Lisp and Dylan are the two that come to mind at the moment), but for all the (vast majority) of languages that lack it, this is a good approach (in some cases you'll find that a centralized map / dict is restrictive and refactor to a Dynamic Visitor design pattern or the like, but thanks to the "getter function" such refactorings will be pretty transparent to all the rest of your code).

Alex Martelli
+1  A: 

In response to your edit of your question, you'll want to look into polymorphism. I personally would have the cando() function be a part of the Board, then, depending on the two classes passed in, the Board would call the appropriate function and return the result (of battle, of seeing, so on and so forth).

If you're doing this in java an enum/interface to go along with your Game Board would be a very clean way of approaching this problem.

Knowles Atchison
+1  A: 

Steve Yegge has an awesome blog post about the Properties pattern that you could use handle this. In fact, he wrote an RPG using it!

http://steve-yegge.blogspot.com/2008/10/universal-design-pattern.html

You might say player1 is a type1 and type1s can attack type2s and player2 is a type2, so unless there is some "override" on the specific player1, player1 can attack player2.

This enables very robust and extensible behavior.

dmo
This sounds like the kind of flexibility I love, as soon as I dig through that huge article!
Timmy
If you love the article, check out GEB as well. Huge, but rewarding.
dmo
It seems all the trivial since I was planning to use Perl.
Timmy
So now that I read it, how would you recommend implementing it using the pattern?
Timmy
dmo
+1  A: 

I suggest you look at double dispatch pattern.

http://c2.com/cgi/wiki?DoubleDispatchExample

The above example explains how a group of printers can print a group of shapes.

http://en.wikipedia.org/wiki/Double_dispatch

The wikipedia example specifically mentions solving adaptive collision problems with this pattern.

Mank
+2  A: 

i would start with a canSee(Monster monster) or canBeSeenBy(Monster monster) method and see what happens. you may end up with a Visibilility class or end up using the http://en.wikipedia.org/wiki/Visitor_pattern. an extreme example is uncle bobs triple dispatch:

// visitor with triple dispatch. from a post to comp.object by robert martin http://www.oma.com
/*
In this case, we are actually using a triple dispatch, because we have two
types to resolve.  The first dispatch is the virtual Collides function which
resolves the type of the object upon which Collides is called.  The second
dispatch is the virtual Accept function which resolves the type of the
object passed into Collides.  Now that we know the type of both objects, we
can call the appropriate global function to calculate the collision.  This
is done by the third and final dispatch to the Visit function.
*/
interface AbstractShape
    {
    boolean Collides(final AbstractShape shape);
    void Accept(ShapeVisitor visitor);
    }
interface ShapeVisitor
    {
    abstract public void Visit(Rectangle rectangle);
    abstract public void Visit(Triangle triangle);
    }
class Rectangle implements AbstractShape
    {
    public boolean Collides(final AbstractShape shape)
     {
     RectangleVisitor visitor=new RectangleVisitor(this);
        shape.Accept(visitor);
        return visitor.result();
        }
    public void Accept(ShapeVisitor visitor)
     { visitor.Visit(this); } // visit Rectangle
    }
class Triangle implements AbstractShape
    {
    public boolean Collides(final AbstractShape shape)
     {
     TriangleVisitor visitor=new TriangleVisitor(this);
     shape.Accept(visitor);
     return visitor.result();
        }
    public void Accept(ShapeVisitor visitor)
     { visitor.Visit(this); } // visit Triangle
    }

class collision
    { // first dispatch
    static boolean Collides(final Triangle t,final Triangle t2) { return true; }
    static boolean Collides(final Rectangle r,final Triangle t) { return true; }
    static boolean Collides(final Rectangle r,final Rectangle r2) { return true; }
    }
// visitors.
class TriangleVisitor implements ShapeVisitor
    {
    TriangleVisitor(final Triangle triangle)
     { this.triangle=triangle; }
    public void Visit(Rectangle rectangle)
     { result=collision.Collides(rectangle,triangle); }
    public void Visit(Triangle triangle)
     { result=collision.Collides(triangle,this.triangle); }
    boolean result() {return result; }
    private boolean result=false;
    private final Triangle triangle;
    }
class RectangleVisitor implements ShapeVisitor
    {
    RectangleVisitor(final Rectangle rectangle)
     { this.rectangle=rectangle; }
    public void Visit(Rectangle rectangle)
     { result=collision.Collides(rectangle,this.rectangle); }
    public void Visit(Triangle triangle)
     { result=collision.Collides(rectangle,triangle); }
    boolean result() {return result; }
    private boolean result=false;
    private final Rectangle rectangle;
    }
public class MartinsVisitor
    {
    public static void main (String[] args)
     {
     Rectangle rectangle=new Rectangle();
     ShapeVisitor visitor=new RectangleVisitor(rectangle);
     AbstractShape shape=new Triangle();
     shape.Accept(visitor);
     }
    }
Ray Tayek