patterncsharpMajor
Rock-Paper-Scissors-Lizard-Spock Challenge
Viewed 0 times
scissorspaperchallengespockrocklizard
Problem
"Scissors cuts paper, paper covers rock,
rock crushes lizard, lizard poisons Spock,
Spock smashes scissors, scissors decapitate lizard,
lizard eats paper, paper disproves Spock,
Spock vaporizes rock. And as it always has, rock crushes scissors."
-- Dr. Sheldon Cooper
So these are the rules.
Building blocks
The first thing I thought was "I need a way to compare the possible selections" - this sounded like
The base class is implemented by each of the possible selections:
```
public class Rock : SelectionBase
{
public override string GetWinningVerb(SelectionBase other)
{
if (other is Scissors) return "crushes";
if (other is Lizard) return "crushes";
throw new InvalidOperationException("Are we playing the same game?");
}
public override int CompareTo(SelectionBase other)
{
if (other is Rock) return 0;
if (other is Paper) return -1;
if (other is Scissors) return 1;
if (other is Lizard) return 1;
if (other is Spock) return -1;
throw new InvalidOperationException("Are we playing the same game?");
}
}
public class Paper : SelectionBase
{
public override string GetWinningVerb(SelectionBase other)
{
if (other is Rock) return "covers";
if (other is Spock) return "
rock crushes lizard, lizard poisons Spock,
Spock smashes scissors, scissors decapitate lizard,
lizard eats paper, paper disproves Spock,
Spock vaporizes rock. And as it always has, rock crushes scissors."
-- Dr. Sheldon Cooper
So these are the rules.
Building blocks
The first thing I thought was "I need a way to compare the possible selections" - this sounded like
IComparable, so I started by implementing that interface in a SelectionBase class. Now because I knew I'd derive Rock, Paper, Scissors, Lizard and Spock classes from this, I decided to include a Name property that returns the type name, and since I also needed a way to display different verbs depending on the type of the opponent's selection, I also included a GetWinningVerb method:public abstract class SelectionBase : IComparable
{
public abstract int CompareTo(SelectionBase other);
public string Name { get { return GetType().Name; } }
public abstract string GetWinningVerb(SelectionBase other);
}The base class is implemented by each of the possible selections:
```
public class Rock : SelectionBase
{
public override string GetWinningVerb(SelectionBase other)
{
if (other is Scissors) return "crushes";
if (other is Lizard) return "crushes";
throw new InvalidOperationException("Are we playing the same game?");
}
public override int CompareTo(SelectionBase other)
{
if (other is Rock) return 0;
if (other is Paper) return -1;
if (other is Scissors) return 1;
if (other is Lizard) return 1;
if (other is Spock) return -1;
throw new InvalidOperationException("Are we playing the same game?");
}
}
public class Paper : SelectionBase
{
public override string GetWinningVerb(SelectionBase other)
{
if (other is Rock) return "covers";
if (other is Spock) return "
Solution
Let's look at your code from an extensibility point of view:
If Sheldon decides to add a new item to the game then you have to go to
So how can we change the design? Well, a game seems to be suited for a rules approach especially since the rules are fairly simple and always of the same structure in this case:
Assuming you have a list of rules somewhere:
You now can make a decision:
If you want to add another item to the game then you add another entry into the
One thing to improve with my version is that rules just effectively define winning rules and all other cases are implicitly ties which in the context of this game makes sense but could be made more explicit.
If Sheldon decides to add a new item to the game then you have to go to
n classes to adjust the comparisons and winning verbs. I usually try to avoid such designs because whenever you require a developer to change stuff in n places when something new is added then he/she is bound to forget one place.So how can we change the design? Well, a game seems to be suited for a rules approach especially since the rules are fairly simple and always of the same structure in this case:
enum Item
{
Rock, Paper, Scissors, Lizard, Spock
}
class Rule
{
public readonly Item Winner;
public readonly Item Loser;
public readonly string WinningPhrase;
public Rule(item winner, string winningPhrase, item loser)
{
Winner = winner;
Loser = loser;
WinningPhrase = winningPhrase;
}
public override string ToString()
{
return string.Format("{0} {1} {2}", Winner, WinningPhrase, Loser);
}
}Assuming you have a list of rules somewhere:
static List Rules = new List {
new Rule(Item.Rock, "crushes", Item.Scissors),
new Rule(Item.Spock, "vaporizes", Item.Rock),
new Rule(Item.Paper, "disproves", Item.Spock),
new Rule(Item.Lizard, "eats", Item.Paper),
new Rule(Item.Scissors, "decapitate", Item.Lizard),
new Rule(Item.Spock, "smashes", Item.Scissors),
new Rule(Item.Lizard, "poisons", Item.Spock),
new Rule(Item.Rock, "crushes", Item.Lizard),
new Rule(Item.Paper, "covers", Item.Rock),
new Rule(Item.Scissors, "cut", Item.Paper)
}You now can make a decision:
class Decision
{
private bool? _HasPlayerWon;
private Rule _WinningRule;
private Decision(bool? hasPlayerWon, Rule winningRule)
{
_HasPlayerWon = hasPlayerWon;
_WinningRule = winningRule;
}
public static Decision Decide(item player, item sheldon)
{
var rule = FindWinningRule(player, sheldon);
if (rule != null)
{
return new Decision(true, rule);
}
rule = FindWinningRule(sheldon, player);
if (rule != null)
{
return new Decision(false, rule);
}
return new Decision(null, null);
}
private static Rule FindWinningRule(item player, item opponent)
{
return Rules.FirstOrDefault(r => r.Winner == player && r.Loser == opponent);
}
public override string ToString()
{
if (_HasPlayerWon == null)
{
return "Meh. Tie!";
}
else if (_HasPlayerWon == true)
{
return string.Format("{0}. You win!", _WinningRule);
}
else
{
return string.Format("{0}. You lose!", _WinningRule);
}
}
}If you want to add another item to the game then you add another entry into the
enum and some additional rules and you are done.One thing to improve with my version is that rules just effectively define winning rules and all other cases are implicitly ties which in the context of this game makes sense but could be made more explicit.
Code Snippets
enum Item
{
Rock, Paper, Scissors, Lizard, Spock
}
class Rule
{
public readonly Item Winner;
public readonly Item Loser;
public readonly string WinningPhrase;
public Rule(item winner, string winningPhrase, item loser)
{
Winner = winner;
Loser = loser;
WinningPhrase = winningPhrase;
}
public override string ToString()
{
return string.Format("{0} {1} {2}", Winner, WinningPhrase, Loser);
}
}static List<Rule> Rules = new List<Rule> {
new Rule(Item.Rock, "crushes", Item.Scissors),
new Rule(Item.Spock, "vaporizes", Item.Rock),
new Rule(Item.Paper, "disproves", Item.Spock),
new Rule(Item.Lizard, "eats", Item.Paper),
new Rule(Item.Scissors, "decapitate", Item.Lizard),
new Rule(Item.Spock, "smashes", Item.Scissors),
new Rule(Item.Lizard, "poisons", Item.Spock),
new Rule(Item.Rock, "crushes", Item.Lizard),
new Rule(Item.Paper, "covers", Item.Rock),
new Rule(Item.Scissors, "cut", Item.Paper)
}class Decision
{
private bool? _HasPlayerWon;
private Rule _WinningRule;
private Decision(bool? hasPlayerWon, Rule winningRule)
{
_HasPlayerWon = hasPlayerWon;
_WinningRule = winningRule;
}
public static Decision Decide(item player, item sheldon)
{
var rule = FindWinningRule(player, sheldon);
if (rule != null)
{
return new Decision(true, rule);
}
rule = FindWinningRule(sheldon, player);
if (rule != null)
{
return new Decision(false, rule);
}
return new Decision(null, null);
}
private static Rule FindWinningRule(item player, item opponent)
{
return Rules.FirstOrDefault(r => r.Winner == player && r.Loser == opponent);
}
public override string ToString()
{
if (_HasPlayerWon == null)
{
return "Meh. Tie!";
}
else if (_HasPlayerWon == true)
{
return string.Format("{0}. You win!", _WinningRule);
}
else
{
return string.Format("{0}. You lose!", _WinningRule);
}
}
}Context
StackExchange Code Review Q#36395, answer score: 46
Revisions (0)
No revisions yet.