tags:

views:

287

answers:

6

I'm writing a game engine in Java for a two player card game for which my students are going to write AI players.

The AI players will take turns playing cards onto their 'field' a part of the 'table' in front of them. They can attack with a card from their field a card in the other player's field. Cards may be face up or face down.

The GameEngine class allows an AI player to take his/her turn by calling the GamePlayer.TakeTurn(GameEngine eng) method. The player can ask the game engine for the defending player's field so the player can make decisions based on the number of cards there and which ones are face up. Let's say this method is GameEngine.GetDefendingField()

Now, I want to make sure that the attacking player cannot modify the defending player's field or the cards in the defending player's field and that the attacking player can only identify the face up cards in the defending players field.

I have classes for Card, Field (an ArrayList of Cards), GamePlayer and GameEngine. Is there any way to create an interface for Field so that the GameEngine can return the defending player's field without the attacking player being able to change it? and without the attacking player being able to recast the defending player's field into something that can be changed.

Can I have GameEngine.GetDefendingField() return a read-only interface to a Field that cannot be recast to a Field?

tia,

Sh-

A: 

You cannot define a Java interface that enforces read-only access to fields/members as that is an implementation detail defined in a class. (Indeed, how you store the data internally is an implementation detail)

I would suggest creating two implementations of your Field interface, one with read-only access and one that has read/edit. You can have each class implement the Field interface, perhaps with an AbstractField class as the parent class of each to cover common functionality.

You can then have constructors for each concrete class (read-only and read/write) to convert between the two, if necessary.

Peter
+2  A: 

You're talking basic Model-View-Controller here, with your Model being Card and Field, the Controller is GamePlayer and GameEngine, and the View is as-of-yet undefined. Your Field is your Model, and you would want to create 2 different interfaces that would access the Field model, one read-only and one read-write. Your read-write implementation would likely just return the Field object. Your read-only implementation would rip thru the elements in the Field and only return the ones that they have access to, doing a deep copy of each Card in the Field returned.

Ed Griebel
+1  A: 

I like the idea of using type safety and polymorphism to control this sort of access control at compile time, especially when teaching.

A good way to approach it would be to have two Field interfaces. Maybe Field and MutableField, where the latter extends the former and adds the change methods. Your FieldImpl could of course implement both.

The problem, of course, is that you want to return different declared types (Field or MutableField) from your game engine based on whose field you are using. If you have a single model, you could use something like getMyField() and getOtherPlayerField() since it appears from the description that when making a call to getField() one knows exactly which field it is that they want.

Uri
Just don't put "I" in front of interface names as this isn't the Java coding standard. See http://stackoverflow.com/questions/541912
Steve Kuo
@Steve: You're right. Bad habit from too many years of Eclipse plugin programming.
Uri
I never noticed that Eclipse puts an "I" in front of interfaces.
Steve Kuo
@Steve: The platform naming convention guide states: For interface names, we follow the "I"-for-interface convention: all interface names are prefixed with an "I". For example, "IWorkspace" or "IIndex". This convention aids code readability by making interface names more readily recognizable. (Microsoft COM interfaces subscribe to this convention).
Uri
@Steve: I misunderstood you. Eclipse will not do that for you. However, the Eclipse SDK's API, which you use to write plugins for Eclipse, and Eclipse itself, uses that as a convention.
Uri
I never knew that. Odd that Eclipse (started off as a Java IDE) follows COM/.NET coding conventions.
Steve Kuo
In think that Eclipse was started before .NET, but I'm not sure. I also think Erich Gamma might have been responsible or at least endorsed this convention. He had a huge influence on the design, and wrote an early book on authoring Eclipse plugins that, if I am not mistaken, used this convention. I think people from a C++ background, like myself, are just used to it.
Uri
+1  A: 

You could create a read-only interface that only has getters. Extending that interface would be read-writer interface that adds setters.

Steve Kuo
Note that this doesn't prevent the AI player from gaining access to the fields by simply casting the object to the read-write interface.
matt b
If the RW interface is a subclass of the RO interface, then you can't cast an RO object to the RW type. In Java, you can only cast objects to types that they implement.
PanCrit
+8  A: 

If you want to achieve this without copying and without the possibility to cast the read-only interface reference to a corresponding read-write interface, you could try to use a wrapper approach.

For an interface like this:

interface Info {
    String getInfoA();
    void setInfoA(String infoA);
    String getInfoB();
    void setInfoB(String infoB);
}

You could create a readonly wrapper which ignores the setters or throws exceptions, like:

class ReadonlyInfo implements Info {
    final Info instance;
    ReadonlyInfo(Info info) { 
         if (null == info) { throw new InvalidArgumentException(); } 
         instance = info; 
    }
    String getInfoA() { return instance.getInfoA(); }
    void setInfoA(String infoA) { /** silent ignore */ }
    String getInfoB() { return instance.getInfoB(); }
    void setInfoB(String infoA) { throw new IllegalStateException(); }
}

By using the wrapper approach you can use polymorphism on the client side, be safe against casting the reference to get more access rights and chose between silently ignore called setters, or throwing an illegal access exception.

Note: of course you will not be safe against code that uses reflection to retrieve the wrapped object.

rsp
+1: Only thing I would do differently is throw UnsupportedOperationException (like the immutable Collection classes do) rather than IllegalStateException.
Adamski
Thanks, I will give this a try tonight and let you know how it went.
shindigo
Absolutely throw UnsupportedOperationException and **not** IllegalStateException. The two indicate quite different conditions.
Software Monkey
+1  A: 

You can definitely do this. It's an approach called attenuation in the Object Capabilities literature. As long as the restricted type isn't defined as a sub-type of the powerful type, casting the object won't provide more authority.

PanCrit