Rock Paper Lizard Spock Monkey Banana
My original post
Based off of Malachi's post
I've changed the way rules work in this version to allow ties to result in double loss or double win. While statistically picking monkey wins against the computer, my intention is to make this a multi-player game which would require more strategy than the completely flat RPS or RPSLS.
I'm looking for any criticism/advice/review on the following code. I think the Rules structure is pretty complex and daunting.
Dictionary<Gesture, Tuple< // A dictionary of all the gestures
Dictionary<Gesture, string>, // A sub-dictionary of all of the gestures this one beats, and a string of why.
Dictionary<Gesture, string> // A sub-dictionary of all of the gestures this one loses to, and a string of why.
>>
I've considered moving it to a separate class. However, it does work perfectly fine as is, so I haven't.
Also you should note that the rules dictionary does contain a reason for why gestures lose, this isn't used in the current implementation, but I do plan to have it used when I have people playing in multi-player to be notified of "why they lost"... which is only slightly different from "why the other guy won".
Here is the full, runnable dump
class Program
{
static void Main(string[] args)
{
var gameMenu = new string[] { "Play", "Clear Score", "Quit" };
var me = new Human();
var computer = new Computer();
var playAgain = true;
do
{
Utils.WriteLineColored("Options:", ConsoleColor.White);
Utils.PrintMenu(gameMenu.ToList());
switch (Utils.PromptForRangedInt(0, gameMenu.Length - 1, "Choose an Option: "))
{
case 0:
Console.Clear();
Game.Play(me, computer);
Console.WriteLine("Your scorecard: " + me.GetScoreCard() + Environment.NewLine);
break;
case 1:
Console.Clear();
me.ClearScore();
Utils.WriteLineColored("Your score has been cleared", ConsoleColor.Green);
break;
case 2:
Console.Clear();
playAgain = false;
Console.Write("Good bye, thanks for playing!\nPress any Key to contine...");
Console.ReadKey(true);
break;
}
} while (playAgain);
}
}
enum Gesture
{
Rock,
Paper,
Scissors,
Lizard,
Spock,
Monkey,
Banana
}
enum Performance
{
Lost,
Tied,
Won
}
abstract class Player
{
public uint Wins { get; private set; }
public uint Loses { get; private set; }
public uint Ties { get; private set; }
public abstract Gesture GetMove();
public string GetScoreCard()
{
return "[Wins: " + Wins + "] [Loses " + Loses + "] [Ties " + Ties + "]";
}
public void ClearScore()
{
Wins = Loses = Ties = 0;
}
public void GiveResult(Performance performance)
{
switch (performance)
{
case Performance.Lost: Loses++; break;
case Performance.Tied: Ties++; break;
case Performance.Won: Wins++; break;
}
}
}
class Human : Player
{
public override Gesture GetMove()
{
Utils.PrintMenu(Game.Gestures.Select(g => g.ToString()).ToList());
return Game.Gestures[Utils.PromptForRangedInt(0, Game.Gestures.Length - 1, "Please choose your Gesture: ")];
}
}
class Computer : Player
{
public override Gesture GetMove()
{
return (Gesture)Game.Gestures.GetValue(new Random().Next(Game.Gestures.Length));
}
}
static class Game
{
public static Gesture[] Gestures = (Gesture[])Enum.GetValues(typeof(Gesture));
private static Dictionary<Gesture, Tuple<Dictionary<Gesture, string>, Dictionary<Gesture, string>>> Rules = new Dictionary<Gesture, Tuple<Dictionary<Gesture, string>, Dictionary<Gesture, string>>>()
{
{Gesture.Rock, Tuple.Create(new Dictionary<Gesture, string>(){
{Gesture.Scissors,"Crushes"},
{Gesture.Lizard,"Crushes"},
{Gesture.Banana,"Smushes"}
}, new Dictionary<Gesture, string>(){
{Gesture.Paper,"Gets Covered By"},
{Gesture.Spock,"Gets Vaporized By"},
{Gesture.Monkey,"Gets Thrown By"},
})},
{Gesture.Paper, Tuple.Create(new Dictionary<Gesture, string>(){
{Gesture.Rock,"Covers"},
{Gesture.Spock,"Disproves"},
{Gesture.Banana,"Slowly Suffocates"}
}, new Dictionary<Gesture, string>(){
{Gesture.Scissors,"Gets Cut By"},
{Gesture.Lizard,"Gets Eaten By"},
{Gesture.Monkey,"Gets Shredded By"},
})},
{Gesture.Scissors, Tuple.Create(new Dictionary<Gesture, string>(){
{Gesture.Paper,"Cut"},
{Gesture.Lizard,"Decapitates"},
{Gesture.Banana,"Cuts"}
}, new Dictionary<Gesture, string>(){
{Gesture.Rock,"Gets Smashed By" },
{Gesture.Spock,"Gets Crushed By"},
{Gesture.Monkey,"Gets Broken By"},
})},
{Gesture.Lizard, Tuple.Create(new Dictionary<Gesture, string>(){
{Gesture.Paper,"Eats"},
{Gesture.Spock,"Poisons"},
{Gesture.Banana,"Eats"}
}, new Dictionary<Gesture, string>(){
{Gesture.Rock,"Gets Crushed By"},
{Gesture.Scissors,"Gets Decapitated By"},
{Gesture.Monkey,"Gets Eaten By"},
})},
{Gesture.Spock, Tuple.Create(new Dictionary<Gesture, string>(){
{Gesture.Rock,"Vaporizes"},
{Gesture.Scissors,"Smashes"},
{Gesture.Banana,"Studies"}
}, new Dictionary<Gesture, string>(){
{Gesture.Paper,"Gets Disproved By"},
{Gesture.Lizard,"Gets Poisoned By"},
{Gesture.Monkey,"Gets Boggled By" },
})},
{Gesture.Monkey, Tuple.Create(new Dictionary<Gesture, string>(){
{Gesture.Rock,"Throws"},
{Gesture.Paper,"Shreds"},
{Gesture.Scissors,"Breaks"},
{Gesture.Lizard,"Steps On"},
{Gesture.Spock,"Boggles"},
}, new Dictionary<Gesture, string>(){
{Gesture.Monkey,"Gets Beaten By"},
{Gesture.Banana,"Goes cRaZy From Eating The"}
})},
{Gesture.Banana, Tuple.Create(new Dictionary<Gesture, string>(){
{Gesture.Monkey,"Crazes"},
{Gesture.Banana,"Chills With"}
}, new Dictionary<Gesture, string>(){
{Gesture.Rock,"Gets Squished By"},
{Gesture.Paper,"Gets Slowly Suffocated By"},
{Gesture.Scissors,"Gets Cut By"},
{Gesture.Lizard,"Gets Eaten By"},
{Gesture.Spock,"Gets Beaten By"}
})}
};
public static void Play(Player player1, Player player2)
{
Gesture p1move = player1.GetMove();
Gesture p2move = player2.GetMove();
Console.Write("Player 1 Chose ");
Utils.WriteLineColored(p1move.ToString(), ConsoleColor.Green);
Console.Write("Player 2 Chose ");
Utils.WriteLineColored(p2move.ToString(), ConsoleColor.Green);
Performance p1perf = WhatHappensToMe(p1move, p2move);
Performance p2perf = WhatHappensToMe(p2move, p1move);
player1.GiveResult(p1perf);
player2.GiveResult(p2perf);
//Report to the console what has happened to player 1
if (p1perf == Performance.Tied)
Console.WriteLine("It was a tie!");
else
Console.WriteLine("Player 1 {0} Because, {1}.", p1perf, GetReason(p1move, p2move, p1perf));
}
private static Performance WhatHappensToMe(Gesture myMove, Gesture theirMove)
{
return
Rules[myMove].Item1.ContainsKey(theirMove) ? Performance.Won :
Rules[myMove].Item2.ContainsKey(theirMove) ? Performance.Lost :
Performance.Tied;
}
private static string GetReason(Gesture myMove, Gesture theirMove, Performance performance)
{
return myMove.ToString() + ' ' +
(performance == Performance.Won ? Rules[myMove].Item1 : Rules[myMove].Item2)[theirMove]
+ ' ' + theirMove.ToString();
}
}
static class Utils
{
public static int PromptForRangedInt(int min = int.MinValue, int max = int.MaxValue, string prompt = "Please enter an Integer: ")
{
int g;
do
{
Console.Write(prompt);
if (int.TryParse(Console.ReadLine(), out g))
{
if (g >= min && g <= max)
return g;
Console.WriteLine("You entered {0}, but the input must be in the range of ({1} - {2}. Please try again...", g, min, max);
}
else
Console.WriteLine("That is not a number. Please try again...");
} while (true);
}
public static void PrintMenu(List<string> values, int baseIndex = 0)
{
values.ForEach(value => Console.WriteLine("{0}: {1}", baseIndex++, value));
}
public static void WriteLineColored(string text, ConsoleColor color)
{
var curr = Console.ForegroundColor;
Console.ForegroundColor = color;
Console.WriteLine(text);
Console.ForegroundColor = curr;
}
}