2

I am looking to implement two branches of classes in my Java program to distinguish between Human and AI-controlled Players in my game. The game is asynchronous, so players have different methods and behavior depending on which type of player they are selected as at the start of the game. All Players have shared methods, and each Player subclass has unique methods that only they can do. In addition, Human players receive prompts to make decisions while AI players make their choices automatically. There can potentially be an infinite number of Player subclasses, and each one will have a Human and AI version.

Since Java doesn't have true multiple inheritance, my current solution is to make use of an AI interface that the AI players inherit, and a Human interface that Human players inherit. Each of these interfaces has a unique makeDecision() method for each potential decision that can be made. Rewriting the code from the decision interfaces is fine, as different types of players shouldn't have the same AI.

Here's a simple example of my code structure:

public interface AI
{
   int chooseNumber(); 
}

public interface Human 
{
    int chooseNumber();
}

public abstract class Player
{ 
    int score;
    void gainPoint()
    { 
        score++;
    }
}

public class RedPlayer extends Player
{
   void doRedThing();
}

public class RedHumanPlayer extends RedPlayer implements Human
{
    int chooseNumber()
    { 
      //Human chooses number
    }
}

public class RedAIPlayer extends RedPlayer implements AI
{
    int chooseNumber()
    {
       return 3;
    }
}

public class BluePlayer extends Player
{
    void doBlueThing();
}

public class BlueHumanPlayer extends BluePlayer implements Human
{
    int chooseNumber()
    { 
      //Human chooses number
    }
}

public class BlueAIPlayer extends BluePlayer implements AI
{
    int chooseNumber()
    {
       return 4;
    }
}

What I want to determine is if this is the best practice for setting up such a system in Java or if there is perhaps a more optimal strategy. I would rather create a proper blueprint before actually doing all the coding.

5
  • 1
    As already mentioned in your SO thread before deleting it there, instead of subclassing a whole tree you could inject a strategy pattern into the player class that upon invocation will perform its behavior. For a human player this could be the probing for input while an AI player could run its pre-assigned rules or some generated plan for deciding which action to perform next. This allows to swap human with AI player and vice-versa easily if a player enters a running game or leaves it early as just the object containing the strategy logic needs to be swapped Oct 31, 2019 at 16:49
  • 3
    I'd suggest not looking for design patterns and don't waste time trying to create a design before you've written any code. A design is something which emerges while you're writing the code. You can't start out with a design pattern and try to fit your code into that pattern - that approach just leads to a broken design that ends up being thrown away and reworked. It's better to just start out writing code which works and without worrying about structure until the design begins to reveal itself to you; after a while you will start to spot the patterns and opportunities to refactor. Oct 31, 2019 at 17:11
  • 1
    @BenCottrell This sounds like an advice to go to the dentist only when it starts to hurt as then it reveals by itself that you should go there. While I admit that something can be over-engineered, and it gets easier to spot design flaws with more experience, spending little time into considering design improvements doesn't hurt either, as cleaning your tooth 3 times a day for 2 minutes. Oct 31, 2019 at 20:44
  • 3
    @RomanVottner To my mind, the question seems like asking the dentist to extract teeth in case of a problem before even having any checks to find out whether there's any need or whether tooth extraction is the right remedy. My advice is about waiting to identify whether a problem exists and having enough information to see what solution is the best fit. I think experience also leads to more consideration of KISS and YAGNI, rather than looking pre-empt particular design patterns, code can be refactored if a better design emerges while writing it. Oct 31, 2019 at 21:43
  • @BenCottrell There is no need to be a dentist to find out that OP’s design intent will soon hurt. An allergy against a design-pattern medication is not a valid argument to advise the patient not to start brushing tooth ;-)
    – Christophe
    Nov 1, 2019 at 13:29

1 Answer 1

3

I think the first misstep in your idea, in the context of the game, is that you aren't really modeling an AI or a Human, you are modeling a Player. You just happen to have different implementations of Player. From the game's perspective there should be no difference between a human and an AI player.

The first thing you need to think of is the interface of the Player, what does a player need to do? We could model it as such:

public interface Player {

    int chooseNumber();

    void gainPoint();
}

Then you can have different types of Players:

public interface BluePlayer extends Player{

    void doBlueThing();

}

public interface RedPlayer extends Player{

    void doRedThing();

}

Now, building on @RomanVettner 's comment you can implement a RedPlayer class and a BluePlayer class which take a RedStrategy and a BlueStrategy respectively and then provide AI and Human versions of the strategies:

public interface Strategy {
    //interface specification
}

public interface RedStrategy extends Strategy {

    void doRedThing();

}

public interface BlueStrategy extends Strategy {

    void doBlueThing();

}

public abstract class BasePlayer<S extends Strategy> implements Player {

    private S strategy;
    private int score;

    public Player(S strategy){
        this.strategy = strategy;
    }

    public updateStrategy(S newStrategy){
        this.strategy = newStrategy;
    }

    public void gainScore(){
        this.score++;
    }

    protected S strategy(){
        return this.strategy;
    }
}

public class RedPlayerImpl extends BasePlayer<RedStrategy> implements RedPlayer {

    public RedPlayerImpl(RedStrategy strategy){
        super(strategy);
    }

    public void doRedThing(){
        this.strategy().toRedThing();
    }
}

public class RedHumanStrategy implements RedStrategy {
    //implementation for Human
}

public class RedAiStrategy implements RedStrategy {
    //implementation for AI
}

//you get the rest
...

Now the game should only every care about the Player interfaces and does not care about how they are implemented.

Moreover, you may consider to simplify the design even further with this approach and avoid the huge number of child classes of Player. The Red and Blue behaviour as well as the AI and Human behaviours can be simply injected into a general Player object at construction time. This would eliminate the need for the different classes RedPlayer, RedAIPlayer, BlueHumanPlayer, etc.

3
  • This is a very nice answer ! It demonstrates the interest to have some thoughts about the design before starting to code wildly :-)
    – Christophe
    Nov 1, 2019 at 13:25
  • 1
    @DocBrown I went with the assumption that doRedThing and doBlueThing are distinct functionality with bad names because it is a toy example. If really we only have doThing then sure we don't need all that extra framework and we can get a lot simpler. This also shows why naming is important and silly made-up examples are pretty bad to build answers for. Nov 2, 2019 at 5:57
  • I took the freedom to add a paragraph of how to eliminate the overuse of inheritance here. Please check if that is ok for you, if not, let me know.
    – Doc Brown
    Nov 2, 2019 at 8:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.