tags:

views:

127

answers:

5

If anyone familiar with Rebecca Wirfs-Brock, she has a piece of Java code found in her book titled, Object Design: Roles, Responsibilities, and Collaborations.

Here is the quote >Applying Double Dispatch to a Specific Problem To implement the game Rock, Paper, Scissors we need to write code that determines whether one object “beats” another. The game has nine possible outcomes based on the three kinds of objects. The number of interactions is the cross product of the kinds of objects. Case or switch statements are often governed by the type of data that is being operated on. The object-oriented language equivalent is to base its actions on the class of some other object. In Java, it looks like this Here is the piece of Java code on page 16 '

  import java.util.*;     
  import java.lang.*;   

 public class Rock
{
public static void main(String args[])
{

}

public static boolean beats(GameObject object)
{
    if (object.getClass.getName().equals("Rock"))
    {
        result = false;
    }
    else if (object.getClass.getName().equals("Paper"))
    {
        result = false;         
    }
    else if(object.getClass.getName().equals("Scissors"))
    {
            result = true;
    }
    return result;
}

}'

===>This is not a very good solution. First, the receiver needs to know too much about the argument. Second, there is one of these nested conditional statements in each of the three classes. If new kinds of objects could be added to the game, each of the three classes would have to be modified. Can anyone share with me how to get this "less than optimal" piece of code to work in order to see it 'working'. She proceeds to demonstrate a better way, but I will spare you. Thanks

+1  A: 

You might want to take a look at this thread:

http://stackoverflow.com/questions/2768748/using-inheritance-and-polymorphism-to-solve-a-common-game-problem

It seems to be around the same subject.

ryanprayogo
+1  A: 

I think personally I would simply have a utility-like class that would contain the 'beats' method. The 'beats' method would take two GameObject objects as parameters.

That way I could just pass in the two objects (rock, paper or scissors) and perform the necessary logic. Now if you add a new object, you don't change anything other than the 'beats' method within the utility class keeping things encapsulated from your main.

ryan's link is nice, it contains several other ideas for handling this situation as well.

Robb
I don't think the main should go where I placed it. It is not present at all in Rebecca's book example. I was just trying to test it out. Since I am new to Java(I'm a .NET coder) should I locate the main in another file? I am using Notepad++ to edit my java files and the command line to compile
Berlioz
A: 

So here's how I fixed it. I first made a new interface called GameObject since they refer to it!

 public interface GameObject
 {
 public boolean beats(GameObject g);
 }

The type didn't exist so referring to it wasn't going to work so great.

Here's my new code for Rock with comments on changes:

 import java.util.*;
 import java.lang.*;

 public class Rock implements GameObject //Need to be an instance of GameObject somehow!
 {
 public static void main(String args[])
     {

     }

 public boolean beats(GameObject object) //This isn't static anymore
 {
 boolean result = false; //Need to declare and initialize result
 if (((Object)object).getClass().getName().equals("Rock")) //getClass should have ()
     { 
     result = false;
     }
 else if (object.getClass().getName().equals("Paper")) //getClass should have ()
     {
     result = false;
     }
 else if(object.getClass().getName().equals("Scissors")) //getClass should have ()
     {
     result = true;
     }
return result;
 }
 }

EDIT: You seemed to be asking for how to fix the code and not the better way to do it. I believe this should be good to go for you now.

JF
Okay, thank you for the change comments and the code, it's great. And YES, I am trying to fix my code to coorelate the 'bad' example she cites in her first chapter of the book. Thanks alot for the code!
Berlioz
Why are folks voting this down? This is what he was looking for. If you have constructive criticism about what's going wrong, leave a comment. Nobody's saying this is the best way to solve the problem, this was an example along the way to a better solution in his book and he had trouble getting it to compile which I fixed.
JF
+2  A: 

Use an enum for dealing with it (RPSObj) that has a beats(RPSObj o) method, with each enum element having a Set passed in, stored as beatset. Then, the beats(RPSObj o) method can do return beatset.contains(o);. Simples :)

Edit: You can actually use an EnumSet as the Set implementation, which should be even more efficient than other set implementations :)

Chris Dennett
I am a .Net coder fooling around with Java in order to learn good object design(GOD). I didn't know you could have a method defined in an Enum. Maybe I am totally ignorant, but I didn't know you could do that in either language.
Berlioz
Nice, simple solution.
digitaljoel
@Berlioz you can, have a look at http://java.sun.com/j2se/1.5.0/docs/guide/language/enums.html
Chris Dennett
+3  A: 

I would start by defining classes RPSSystem and RPSObject. The code to construct the classic RPS-game would look like this:

RPSObject rock = new RPSObject("Rock");
RPSObject paper = new RPSObject("Paper");
RPSObject scissors = new RPSObject("Scissors");
RPSSystem classicRPS = new RPSSystem(rock, paper, scissors);
// new RPSSystem(Collection<RPSObject> objects) possible too
classicRPS.defineBeatsRule(rock, scissors);
classicRPS.defineBeatsRule(paper, rock);
classicRPS.defineBeatsRule(scissors, paper);

RPSSystem would have a method

int fight(RPSObject a, RPSObject b)

which would return -1 when a wins, 1 when b wins and 0 when the result is not defined. Internally RPSObjects could be stored in a list and beating rules could be stored in a matrix (columns and rows would match the indices of the objects in the list). If multiple instances of similar RPSObject should be allowed, the equals-method of RPSObject should be written accordingly.

Having a separate class for each object in the system seems a bit too complicated.

EDIT:

Complete classes:

package rpsgame;

public final class RPSObject {
    private final String name;

    public RPSObject(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public String toString() {
        return getName();
    }
}

package rpsgame;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public final class RPSSystem {

    private final List<RPSObject> objects;
    private final int[][] beatsRules;

    public static final int WINS = 1;
    public static final int TIE = 0;
    public static final int LOSES = -1;


    public RPSSystem(RPSObject... objects) {
        this.objects = Arrays.asList(objects.clone());
        this.beatsRules = new int[objects.length][objects.length];
    }

    void defineBeatsRule(RPSObject winner, RPSObject loser) {
        if (winner.equals(loser)) throw new IllegalArgumentException();

        int winnerIndex = getObjectIndex(winner);
        int loserIndex = getObjectIndex(loser);

        beatsRules[winnerIndex][loserIndex] = WINS;
        beatsRules[loserIndex][winnerIndex] = LOSES;
    }

    public int fight(RPSObject a, RPSObject b) {
        int aIndex = getObjectIndex(a);
        int bIndex = getObjectIndex(b);

        return beatsRules[aIndex][bIndex];
    }

    public List<RPSObject> getObjects() {
        return Collections.unmodifiableList(objects);
    }

    private int getObjectIndex(RPSObject o) {
        int index = objects.indexOf(o);
        if (index < 0) throw new IllegalArgumentException();
        return index;
    }

    // test
    public static void main(String[] args) {

        RPSSystem classicRPS = buildClassicRPS();

        List<RPSObject> objects = classicRPS.getObjects();

        for (RPSObject a: objects) {
            for (RPSObject b: objects) {
                int result = classicRPS.fight(a, b);
                switch (result) {
                    case RPSSystem.WINS:
                        System.out.println(a + " beats " + b);
                        break;
                    case RPSSystem.TIE:
                        System.out.println(a + " vs " + b + " is tied");
                        break;
                    case RPSSystem.LOSES:
                        System.out.println(a + " loses against " + b);
                        break;
                }   
            }
        }
    }

    private static RPSSystem buildClassicRPS() {
        RPSObject rock = new RPSObject("Rock");
        RPSObject paper = new RPSObject("Paper");
        RPSObject scissors = new RPSObject("Scissors");

        RPSSystem classicRPS = new RPSSystem(rock, paper, scissors);

        classicRPS.defineBeatsRule(rock, scissors);
        classicRPS.defineBeatsRule(paper, rock);
        classicRPS.defineBeatsRule(scissors, paper);
        return classicRPS;
    }
}

Just add RPSSystem.EVERYONE_DIES and defineEveryoneDiesRule(...) and you're ready for

rps.add(atombomb);
rps.defineBeatsRule(atombomb, scissors);
rps.defineBeatsRule(atombomb, rock);
rps.defineBeatsRule(atombomb, paper);
rps.defineEveryoneDiesRule(atombomb, atombomb);
COME FROM
Thank you for sharing your code with me.
Berlioz