diff --git a/94 War/csharp/War/War.sln b/94 War/csharp/War/War.sln new file mode 100644 index 00000000..af1e88a5 --- /dev/null +++ b/94 War/csharp/War/War.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31112.23 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "War", "War\War.csproj", "{C13BE0FA-D8F7-4CA7-A95D-DA03A9DE8950}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WarTester", "WarTester\WarTester.csproj", "{B539F618-EE83-486C-9A6D-404E998BED2D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C13BE0FA-D8F7-4CA7-A95D-DA03A9DE8950}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C13BE0FA-D8F7-4CA7-A95D-DA03A9DE8950}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C13BE0FA-D8F7-4CA7-A95D-DA03A9DE8950}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C13BE0FA-D8F7-4CA7-A95D-DA03A9DE8950}.Release|Any CPU.Build.0 = Release|Any CPU + {B539F618-EE83-486C-9A6D-404E998BED2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B539F618-EE83-486C-9A6D-404E998BED2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B539F618-EE83-486C-9A6D-404E998BED2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B539F618-EE83-486C-9A6D-404E998BED2D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3FA273C6-089E-4F3D-8DC0-F9143D1CDF3A} + EndGlobalSection +EndGlobal diff --git a/94 War/csharp/War/War/Cards.cs b/94 War/csharp/War/War/Cards.cs new file mode 100644 index 00000000..99e03020 --- /dev/null +++ b/94 War/csharp/War/War/Cards.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; + + + +namespace War +{ + // These enums define the card's suit and rank. + public enum Suit + { + clubs, + diamonds, + hearts, + spades + } + + public enum Rank + { + // Skip 1 because ace is high. + two = 2, + three, + four, + five, + six, + seven, + eight, + nine, + ten, + jack, + queen, + king, + ace + } + + // A class to represent a playing card. + public class Card + { + // A card is an immutable object (i.e. it can't be changed) so its suit + // and rank value are readonly; they can only be set in the constructor. + private readonly Suit suit; + private readonly Rank rank; + + // These dictionaries are used to convert a suit or rank value into a string. + private readonly Dictionary suitNames = new Dictionary() + { + { Suit.clubs, "C"}, + { Suit.diamonds, "D"}, + { Suit.hearts, "H"}, + { Suit.spades, "S"}, + }; + + private readonly Dictionary rankNames = new Dictionary() + { + { Rank.two, "2"}, + { Rank.three, "3"}, + { Rank.four, "4"}, + { Rank.five, "5"}, + { Rank.six, "6"}, + { Rank.seven, "7"}, + { Rank.eight, "8"}, + { Rank.nine, "9"}, + { Rank.ten, "10"}, + { Rank.jack, "J"}, + { Rank.queen, "Q"}, + { Rank.king, "K"}, + { Rank.ace, "A"}, + }; + + public Card(Suit suit, Rank rank) + { + this.suit = suit; + this.rank = rank; + } + + // Relational Operator Overloading. + // + // You would normally expect the relational operators to consider both the suit and the + // rank of a card, but in this program suit doesn't matter so we define the operators to just + // compare rank. + + // When adding relational operators we would normally include == and != but they are not + // relevant to this program so haven't been defined. Note that if they were defined we + // should also override the Equals() and GetHashCode() methods. See, for example: + // http://www.blackwasp.co.uk/CSharpRelationalOverload.aspx + + // If the == and != operators were defined they would look like this: + // + //public static bool operator ==(Card lhs, Card rhs) + //{ + // return lhs.rank == rhs.rank; + //} + // + //public static bool operator !=(Card lhs, Card rhs) + //{ + // return !(lhs == rhs); + //} + + public static bool operator <(Card lhs, Card rhs) + { + return lhs.rank < rhs.rank; + } + + public static bool operator >(Card lhs, Card rhs) + { + return rhs < lhs; + } + + public static bool operator <=(Card lhs, Card rhs) + { + return !(lhs > rhs); + } + + public static bool operator >=(Card lhs, Card rhs) + { + return !(lhs < rhs); + } + + public override string ToString() + { + // N.B. We are using string interpolation to create the card name. + return $"{suitNames[suit]}-{rankNames[rank]}"; + } + } + + // A class to represent a deck of cards. + public class Deck + { + public const int deckSize = 52; + + private Card[] theDeck = new Card[deckSize]; + + public Deck() + { + // Populate theDeck with all the cards in order. + int i = 0; + for (Suit suit = Suit.clubs; suit <= Suit.spades; suit++) + { + for (Rank rank = Rank.two; rank <= Rank.ace; rank++) + { + theDeck[i] = new Card(suit, rank); + i++; + } + } + } + + // Return the card at a particular position in the deck. + // N.B. As this is such a short method, we make it an + // expression-body method. + public Card GetCard(int i) => theDeck[i]; + + // Shuffle the cards, this uses the modern version of the + // Fisher-Yates shuffle, see: + // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm + public void Shuffle() + { + var rand = new Random(); + + // Iterate backwards through the deck. + for (int i = deckSize - 1; i >= 1; i--) + { + int j = rand.Next(0, i); + + // Swap the cards at i and j + Card temp = theDeck[j]; + theDeck[j] = theDeck[i]; + theDeck[i] = temp; + } + } + } +} diff --git a/94 War/csharp/War/War/Program.cs b/94 War/csharp/War/War/Program.cs new file mode 100644 index 00000000..7ab335e3 --- /dev/null +++ b/94 War/csharp/War/War/Program.cs @@ -0,0 +1,35 @@ +namespace War +{ + class Program + { + static void Main(string[] args) + { + var ui = new UserInterface(); + ui.WriteIntro(); + + var deck = new Deck(); + deck.Shuffle(); + + int yourScore = 0; + int computersScore = 0; + bool usedAllCards = true; + + for (int i = 0; i < Deck.deckSize; i += 2) + { + // Play the next hand. + var yourCard = deck.GetCard(i); + var computersCard = deck.GetCard(i + 1); + + ui.WriteAResult(yourCard, computersCard, ref computersScore, ref yourScore); + + if (!ui.AskAQuestion("DO YOU WANT TO CONTINUE? ")) + { + usedAllCards = false; + break; + } + } + + ui.WriteClosingRemarks(usedAllCards, yourScore, computersScore); + } + } +} diff --git a/94 War/csharp/War/War/UserInterface.cs b/94 War/csharp/War/War/UserInterface.cs new file mode 100644 index 00000000..ee93d9fa --- /dev/null +++ b/94 War/csharp/War/War/UserInterface.cs @@ -0,0 +1,83 @@ +using System; + + + +namespace War +{ + // This class displays all the text that the user sees when playing the game. + // It also handles asking the user a yes/no question and returning their answer. + public class UserInterface + { + public void WriteIntro() + { + Console.WriteLine(" WAR"); + Console.WriteLine(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); + Console.WriteLine(); + + Console.WriteLine("THIS IS THE CARD GAME OF WAR. EACH CARD IS GIVEN BY SUIT-#"); + Console.Write("AS S-7 FOR SPADE 7. "); + + if (AskAQuestion("DO YOU WANT DIRECTIONS? ")) + { + Console.WriteLine("THE COMPUTER GIVES YOU AND IT A 'CARD'. THE HIGHER CARD"); + Console.WriteLine("(NUMERICALLY) WINS. THE GAME ENDS WHEN YOU CHOOSE NOT TO"); + Console.WriteLine("CONTINUE OR WHEN YOU HAVE FINISHED THE PACK."); + } + + Console.WriteLine(); + Console.WriteLine(); + } + + public void WriteAResult(Card yourCard, Card computersCard, ref int computersScore, ref int yourScore) + { + Console.WriteLine($"YOU: {yourCard} COMPUTER: {computersCard}"); + if (yourCard < computersCard) + { + computersScore++; + Console.WriteLine($"THE COMPUTER WINS!!! YOU HAVE {yourScore} AND THE COMPUTER HAS {computersScore}"); + } + else if (yourCard > computersCard) + { + yourScore++; + Console.WriteLine($"YOU WIN. YOU HAVE {yourScore} AND THE COMPUTER HAS {computersScore}"); + } + else + { + Console.WriteLine("TIE. NO SCORE CHANGE"); + } + } + + public bool AskAQuestion(string question) + { + // Repeat asking the question until the user answers "YES" or "NO". + while (true) + { + Console.Write(question); + string result = Console.ReadLine(); + + if (result.ToLower() == "yes") + { + Console.WriteLine(); + return true; + } + else if (result.ToLower() == "no") + { + Console.WriteLine(); + return false; + } + + Console.WriteLine("YES OR NO, PLEASE."); + } + } + + public void WriteClosingRemarks(bool usedAllCards, int yourScore, int computersScore) + { + if (usedAllCards) + { + Console.WriteLine("WE HAVE RUN OUT OF CARDS."); + } + Console.WriteLine($"FINAL SCORE: YOU: {yourScore} THE COMPUTER: {computersScore}"); + Console.WriteLine("THANKS FOR PLAYING. IT WAS FUN."); + } + } +} diff --git a/94 War/csharp/War/War/War.csproj b/94 War/csharp/War/War/War.csproj new file mode 100644 index 00000000..c73e0d16 --- /dev/null +++ b/94 War/csharp/War/War/War.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp3.1 + + + diff --git a/94 War/csharp/War/WarTester/Tests.cs b/94 War/csharp/War/WarTester/Tests.cs new file mode 100644 index 00000000..387b4547 --- /dev/null +++ b/94 War/csharp/War/WarTester/Tests.cs @@ -0,0 +1,132 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Text; +using War; + + + +namespace WarTester +{ + [TestClass] + public class CardTest + { + private Card c1 = new Card(Suit.clubs, Rank.two); + private Card c2 = new Card(Suit.clubs, Rank.ten); + private Card c3 = new Card(Suit.diamonds, Rank.ten); + private Card c4 = new Card(Suit.diamonds, Rank.ten); + + // Test the relational operators. + + [TestMethod] + public void LessThanIsValid() + { + Assert.IsTrue(c1 < c2, "c1 < c2"); // Same suit, different rank. + Assert.IsFalse(c2 < c1, "c2 < c1"); // Same suit, different rank. + + Assert.IsFalse(c3 < c4, "c3 < c4"); // Same suit, same rank. + + Assert.IsTrue(c1 < c3, "c1 < c3"); // Different suit, different rank. + Assert.IsFalse(c3 < c1, "c3 < c1"); // Different suit, different rank. + + Assert.IsFalse(c2 < c4, "c2 < c4"); // Different suit, same rank. + Assert.IsFalse(c4 < c2, "c4 < c2"); // Different suit, same rank. + } + + [TestMethod] + public void GreaterThanIsValid() + { + Assert.IsFalse(c1 > c2, "c1 > c2"); // Same suit, different rank. + Assert.IsTrue(c2 > c1, "c2 > c1"); // Same suit, different rank. + + Assert.IsFalse(c3 > c4, "c3 > c4"); // Same suit, same rank. + + Assert.IsFalse(c1 > c3, "c1 > c3"); // Different suit, different rank. + Assert.IsTrue(c3 > c1, "c3 > c1"); // Different suit, different rank. + + Assert.IsFalse(c2 > c4, "c2 > c4"); // Different suit, same rank. + Assert.IsFalse(c4 > c2, "c4 > c2"); // Different suit, same rank. + } + + [TestMethod] + public void LessThanEqualsIsValid() + { + Assert.IsTrue(c1 <= c2, "c1 <= c2"); // Same suit, different rank. + Assert.IsFalse(c2 <= c1, "c2 <= c1"); // Same suit, different rank. + + Assert.IsTrue(c3 <= c4, "c3 <= c4"); // Same suit, same rank. + + Assert.IsTrue(c1 <= c3, "c1 <= c3"); // Different suit, different rank. + Assert.IsFalse(c3 <= c1, "c3 <= c1"); // Different suit, different rank. + + Assert.IsTrue(c2 <= c4, "c2 <= c4"); // Different suit, same rank. + Assert.IsTrue(c4 <= c2, "c4 <= c2"); // Different suit, same rank. + } + + [TestMethod] + public void GreaterThanEqualsIsValid() + { + Assert.IsFalse(c1 >= c2, "c1 >= c2"); // Same suit, different rank. + Assert.IsTrue(c2 >= c1, "c2 >= c1"); // Same suit, different rank. + + Assert.IsTrue(c3 >= c4, "c3 >= c4"); // Same suit, same rank. + + Assert.IsFalse(c1 >= c3, "c1 >= c3"); // Different suit, different rank. + Assert.IsTrue(c3 >= c1, "c3 >= c1"); // Different suit, different rank. + + Assert.IsTrue(c2 >= c4, "c2 >= c4"); // Different suit, same rank. + Assert.IsTrue(c4 >= c2, "c4 >= c2"); // Different suit, same rank. + } + + [TestMethod] + public void ToStringIsValid() + { + var s1 = c1.ToString(); + var s2 = c3.ToString(); + var s3 = new Card(Suit.hearts, Rank.queen).ToString(); + var s4 = new Card(Suit.spades, Rank.ace).ToString(); + + Assert.IsTrue(s1 == "C-2", "s1 invalid"); + Assert.IsTrue(s2 == "D-10", "s2 invalid"); + Assert.IsTrue(s3 == "H-Q", "s3 invalid"); + Assert.IsTrue(s4 == "S-A", "s4 invalid"); + } + } + + [TestClass] + public class DeckTest + { + private readonly string cardNamesInOrder = "C-2C-3C-4C-5C-6C-7C-8C-9C-10C-JC-QC-KC-AD-2D-3D-4D-5D-6D-7D-8D-9D-10D-JD-QD-KD-AH-2H-3H-4H-5H-6H-7H-8H-9H-10H-JH-QH-KH-AS-2S-3S-4S-5S-6S-7S-8S-9S-10S-JS-QS-KS-A"; + + //Helper method. Adds the names of all the cards together into a single string. + private string ConcatenateTheDeck(Deck d) + { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < Deck.deckSize; i++) + { + sb.Append(d.GetCard(i)); + } + + return sb.ToString(); + } + + [TestMethod] + public void InitialDeckContainsCardsInOrder() + { + Deck d = new Deck(); + string allTheCards = ConcatenateTheDeck(d); + + Assert.IsTrue(allTheCards == cardNamesInOrder); + } + + [TestMethod] + public void ShufflingChangesDeck() + { + // I'm not sure how to test that shuffling has worked other than to check that the cards aren't in the initial order. + Deck d = new Deck(); + d.Shuffle(); + string allTheCards = ConcatenateTheDeck(d); + + Assert.IsTrue(allTheCards != cardNamesInOrder); + } + } +} diff --git a/94 War/csharp/War/WarTester/WarTester.csproj b/94 War/csharp/War/WarTester/WarTester.csproj new file mode 100644 index 00000000..9d1808d6 --- /dev/null +++ b/94 War/csharp/War/WarTester/WarTester.csproj @@ -0,0 +1,29 @@ + + + + netcoreapp3.1 + + false + + AnyCPU + + + + true + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + +