"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 IComparable<T>
, 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<SelectionBase>
{
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 "disproves";
throw new InvalidOperationException("Are we playing the same game?");
}
public override int CompareTo(SelectionBase other)
{
if (other is Rock) return 1;
if (other is Paper) return 0;
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 Scissors : SelectionBase
{
public override string GetWinningVerb(SelectionBase other)
{
if (other is Paper) return "cuts";
if (other is Lizard) return "decapitates";
throw new InvalidOperationException("Are we playing the same game?");
}
public override int CompareTo(SelectionBase other)
{
if (other is Rock) return -1;
if (other is Paper) return 1;
if (other is Scissors) return 0;
if (other is Lizard) return 1;
if (other is Spock) return -1;
throw new InvalidOperationException("Are we playing the same game?");
}
}
public class Lizard : SelectionBase
{
public override string GetWinningVerb(SelectionBase other)
{
if (other is Paper) return "eats";
if (other is Spock) return "poisons";
throw new InvalidOperationException("Are we playing the same game?");
}
public override int CompareTo(SelectionBase other)
{
if (other is Rock) return -1;
if (other is Paper) return 1;
if (other is Scissors) return -1;
if (other is Lizard) return 0;
if (other is Spock) return 1;
throw new InvalidOperationException("Are we playing the same game?");
}
}
public class Spock : SelectionBase
{
public override string GetWinningVerb(SelectionBase other)
{
if (other is Rock) return "vaporizes";
if (other is Scissors) return "smashes";
throw new InvalidOperationException("Are we playing the same game?");
}
public override int CompareTo(SelectionBase other)
{
if (other is Rock) return 1;
if (other is Paper) return -1;
if (other is Scissors) return 1;
if (other is Lizard) return -1;
if (other is Spock) return 0;
throw new InvalidOperationException("Are we playing the same game?");
}
}
User Input
Then I needed a way to get user input. I knew I was going to make a simple console app, but just so I could run unit tests I decided to create an IUserInputProvider
interface - the first pass had all 3 methods in the interface, but since I wasn't using them all, I only kept one; I don't think getting rid of GetUserInput(string)
would hurt:
public interface IUserInputProvider
{
string GetValidUserInput(string prompt, IEnumerable<string> validValues);
}
public class ConsoleUserInputProvider : IUserInputProvider
{
private string GetUserInput(string prompt)
{
Console.WriteLine(prompt);
return Console.ReadLine();
}
private string GetUserInput(string prompt, IEnumerable<string> validValues)
{
var input = GetUserInput(prompt);
var isValid = validValues.Select(v => v.ToLower()).Contains(input.ToLower());
return isValid ? input : string.Empty;
}
public string GetValidUserInput(string prompt, IEnumerable<string> validValues)
{
var input = string.Empty;
var isValid = false;
while (!isValid)
{
input = GetUserInput(prompt, validValues);
isValid = !string.IsNullOrEmpty(input) || validValues.Contains(string.Empty);
}
return input;
}
}
The actual program
class Program
{
/*
"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
*/
static void Main(string[] args)
{
var consoleReader = new ConsoleUserInputProvider();
var consoleWriter = new ConsoleResultWriter();
var game = new Game(consoleReader, consoleWriter);
game.Run();
}
}
IResultWriter
public interface IResultWriter
{
void OutputResult(int comparisonResult, SelectionBase player, SelectionBase sheldon);
}
public class ConsoleResultWriter : IResultWriter
{
public void OutputResult(int comparisonResult, SelectionBase player, SelectionBase sheldon)
{
var resultActions = new Dictionary<int, Action<SelectionBase, SelectionBase>>
{
{ 1, OutputPlayerWinsResult },
{ -1, OutputPlayerLosesResult },
{ 0, OutputTieResult }
};
resultActions[comparisonResult].Invoke(player, sheldon);
}
private void OutputPlayerLosesResult(SelectionBase player, SelectionBase sheldon)
{
Console.WriteLine("\n\tSheldon says: \"{0} {1} {2}. You lose!\"\n", sheldon.Name, sheldon.GetWinningVerb(player), player.Name);
}
private void OutputPlayerWinsResult(SelectionBase player, SelectionBase sheldon)
{
Console.WriteLine("\n\tSheldon says: \"{0} {1} {2}. You win!\"\n", player.Name, player.GetWinningVerb(sheldon), sheldon.Name);
}
private void OutputTieResult(SelectionBase player, SelectionBase sheldon)
{
Console.WriteLine("\n\tSheldon says: \"Meh. Tie!\"\n");
}
The actual game
I didn't bother with building a complex AI - we're playing against Sheldon Cooper here, and he systematically plays Spock.
public class Game
{
private readonly Dictionary<string, SelectionBase> _playable =
new Dictionary<string, SelectionBase>
{
{ "1", new Rock() },
{ "2", new Paper() },
{ "3", new Scissors() },
{ "4", new Lizard() },
{ "5", new Spock() }
};
private readonly IUserInputProvider _consoleInput;
private readonly IResultWriter _resultWriter;
public Game(IUserInputProvider console, IResultWriter resultWriter)
{
_consoleInput = console;
_resultWriter = resultWriter;
}
public void Run()
{
while (true)
{
LayoutGameScreen();
var player = GetUserSelection();
if (player == null) return;
var sheldon = new Spock();
var result = player.CompareTo(sheldon);
_resultWriter.OutputResult(result, player, sheldon);
Pause();
}
}
private void Pause()
{
Console.WriteLine("\nPress <ENTER> to continue.");
Console.ReadLine();
}
private void LayoutGameScreen()
{
Console.Clear();
Console.WriteLine("Rock-Paper-Scissors-Lizard-Spock 1.0\n{0}\n", new string('=', 40));
foreach (var item in _playable)
Console.WriteLine("\t[{0}] {1}", item.Key, item.Value.Name);
Console.WriteLine();
}
private SelectionBase GetUserSelection()
{
var values = _playable.Keys.ToList();
values.Add(string.Empty); // allows a non-selection
var input = _consoleInput.GetValidUserInput("Your selection? <ENTER> to quit.", values);
if (input == string.Empty) return null;
return _playable[input];
}
}
Output
Rock
: "Spock vaporizes Rock. You lose!"Paper
: "Paper disproves Spock. You win!"Scissors
: "Spock smashes Scissors. You lose!"Lizard
: "Lizard poisons Spock. You win!"Spock
: "Meh. Tie!"