Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can one describe a rock-paper-scissors relationship between 3 items?

Tags:

oop

php

Let's say I have the following structure:

abstract class Hand {}

class Rock extends Hand {}
class Paper extends Hand {}
class Scissors extends Hand {}

The goal is to make a function (or a method) Hand::compareHands(Hand $hand1, Hand $hand2), which would return the winning hand in a rock-paper-scissors match.

That would be very easy with a bunch of ifs, but the point is to have a more robust structure, that's relying on polymorphism rather than on procedural code.

P.S. this is done in actual production code, if someone is asking. This isn't some sort of challenge or homework. (It's not really rock-paper-scissors, but you get the point).

like image 675
Madara's Ghost Avatar asked Oct 03 '12 21:10

Madara's Ghost


2 Answers

The sole nature of your hand is that it is beating one single other one.

Then you want to not repeat the code while having one concrete type per hand-form, so you need to parametrize. Depending on the level of freedom you can allow, this can be as simple as a protected member:

abstract class Hand {

    protected $beats;
    
    final public function beats(Hand $opponent) {
    
        return $opponent instanceof $this->beats;
    }
}

class Rock extends Hand {

    protected beats = 'Scissors';
}

class Paper extends Hand {

    protected beats = 'Rock';
}

class Scissors extends Hand {

    protected beats = 'Paper';
}

I think this is the standard template method pattern here, in a very simple form.

Compare this with Lusitanian's answer who should get the credits for the actual code, I just have re-sorted it a little bit. But only a very little.

Additionally I need to give credits to @Leigh for the far better function and parameter naming. This should reduce the need of comments.

The second alternative that Lusistanian suggests can be represented with the strategy pattern. It is also somewhat straight forward:

class EvaluateHands
{
    private $rules;

    public function __construct(array $rules)
    {
        $this->rules = $rules;
    }

    public function compareHands(Hand $hand1, Hand $hand2)
    {
        return $this->rules[get_class($hand1)] === get_class($hand2) ? $hand1 : $hand2;
    }
}

new EvaluateHands(
    array(
        'Rock' => 'Scissors',
        'Paper' => 'Rock',
        'Scissor' => 'Paper'
    )
);

The comparison between two hands has been fully encapsulated into the EvaluateHands type which is even configureable (if the rules of the game change), while the hands would stay the same:

abstract class Hand {}

class Rock extends Hand {}

class Paper extends Hand {}

class Scissors extends Hand {}

Credits for this code go to gordon (next to Lusistanian).

like image 184
hakre Avatar answered Sep 20 '22 12:09

hakre


How about this?

class Scissors extends Hand implements Beats<Paper> {}

where Beats<> is a generic interface whose signature looks like:

interface Beats<Hand> {}
like image 39
Chris Laplante Avatar answered Sep 20 '22 12:09

Chris Laplante