From b4655d577745f9aa03e1e7ffc9fa5e3c5b9ca813 Mon Sep 17 00:00:00 2001 From: rbamforth <79797573+rbamforth@users.noreply.github.com> Date: Fri, 26 Mar 2021 22:51:28 +0000 Subject: [PATCH] Moved UI stuff into its own class. Minor changes, commenting and tidying up the other classes. --- 94 War/csharp/War/War/Cards.cs | 52 +++++++++++----- 94 War/csharp/War/War/Program.cs | 79 +++--------------------- 94 War/csharp/War/War/UserInterface.cs | 83 ++++++++++++++++++++++++++ 94 War/csharp/War/WarTester/Tests.cs | 30 ++++++---- 4 files changed, 145 insertions(+), 99 deletions(-) create mode 100644 94 War/csharp/War/War/UserInterface.cs diff --git a/94 War/csharp/War/War/Cards.cs b/94 War/csharp/War/War/Cards.cs index 7c31c3b2..99e03020 100644 --- a/94 War/csharp/War/War/Cards.cs +++ b/94 War/csharp/War/War/Cards.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; -using System.Text; + + namespace War { + // These enums define the card's suit and rank. public enum Suit { - none = 0, clubs, diamonds, hearts, @@ -15,7 +16,6 @@ namespace War public enum Rank { - none = 0, // Skip 1 because ace is high. two = 2, three, @@ -32,25 +32,25 @@ namespace War ace } - // TODO Testing - + // 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; - private static Dictionary suitNames = new Dictionary() + // These dictionaries are used to convert a suit or rank value into a string. + private readonly Dictionary suitNames = new Dictionary() { - { Suit.none, "N"}, { Suit.clubs, "C"}, { Suit.diamonds, "D"}, { Suit.hearts, "H"}, { Suit.spades, "S"}, }; - private static Dictionary rankNames = new Dictionary() + private readonly Dictionary rankNames = new Dictionary() { - { Rank.none, "0"}, { Rank.two, "2"}, { Rank.three, "3"}, { Rank.four, "4"}, @@ -66,18 +66,30 @@ namespace War { Rank.ace, "A"}, }; - public Card(Suit suit, Rank rank) // immutable + public Card(Suit suit, Rank rank) { this.suit = suit; this.rank = rank; } - // would normally consider suit and rank but in this case we only want to compare 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); @@ -105,10 +117,12 @@ namespace War public override string ToString() { - return $"{suitNames[suit]}-{rankNames[rank]}"; // string interpolation + // 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; @@ -117,6 +131,7 @@ namespace War public Deck() { + // Populate theDeck with all the cards in order. int i = 0; for (Suit suit = Suit.clubs; suit <= Suit.spades; suit++) { @@ -128,14 +143,21 @@ namespace War } } + // 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() { - // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle + var rand = new Random(); + + // Iterate backwards through the deck. for (int i = deckSize - 1; i >= 1; i--) { - var rand = new Random(); int j = rand.Next(0, i); // Swap the cards at i and j diff --git a/94 War/csharp/War/War/Program.cs b/94 War/csharp/War/War/Program.cs index 68b602f8..57a6c37e 100644 --- a/94 War/csharp/War/War/Program.cs +++ b/94 War/csharp/War/War/Program.cs @@ -1,58 +1,11 @@ -using System; - -namespace War +namespace War { - public class Intro - { - public void WriteIntro() - { - Console.WriteLine(" WAR"); - Console.WriteLine(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); - Console.WriteLine(); - Console.WriteLine(); - Console.WriteLine(); - - Console.WriteLine("THIS IS THE CARD GAME OF WAR. EACH CARD IS GIVEN BY SUIT-#"); - Console.WriteLine("AS S-7 FOR SPADE 7. "); - - if (AskQuestion("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 bool AskQuestion(string question) - { - while (true) - { - Console.Write(question); - string result = Console.ReadLine(); - - if (result.ToLower()[0] == 'y') - { - return true; - } - else /*if (result.ToLower() == "no")*/ - { - return false; - } - - Console.WriteLine("YES OR NO, PLEASE."); - } - } - } - class Program { static void Main(string[] args) { - var intro = new Intro(); - intro.WriteIntro(); + var ui = new UserInterface(); + ui.WriteIntro(); var deck = new Deck(); deck.Shuffle(); @@ -63,38 +16,20 @@ namespace War for (int i = 0; i < Deck.deckSize; i += 2) { + // Play the next hand. var yourCard = deck.GetCard(i); var computersCard = deck.GetCard(i + 1); - 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"); - } + ui.WriteAResult(yourCard, computersCard, ref computersScore, ref yourScore); - if (!intro.AskQuestion("DO YOU WANT TO CONTINUE? ")) + if (!ui.AskAQuestion("DO YOU WANT TO CONTINUE? ")) { usedAllCards = false; break; } } - 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."); + 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/WarTester/Tests.cs b/94 War/csharp/War/WarTester/Tests.cs index 313d2a1e..387b4547 100644 --- a/94 War/csharp/War/WarTester/Tests.cs +++ b/94 War/csharp/War/WarTester/Tests.cs @@ -14,6 +14,8 @@ namespace WarTester 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() { @@ -77,10 +79,10 @@ namespace WarTester [TestMethod] public void ToStringIsValid() { - string s1 = c1.ToString(); - string s2 = c3.ToString(); - string s3 = new Card(Suit.hearts, Rank.queen).ToString(); - string s4 = new Card(Suit.spades, Rank.ace).ToString(); + 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"); @@ -92,7 +94,10 @@ namespace WarTester [TestClass] public class DeckTest { - private string ConcatenateDeck(Deck d) + 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(); @@ -107,20 +112,21 @@ namespace WarTester [TestMethod] public void InitialDeckContainsCardsInOrder() { - Deck d1 = new Deck(); - string allTheCards = ConcatenateDeck(d1); + Deck d = new Deck(); + string allTheCards = ConcatenateTheDeck(d); - Assert.IsTrue(allTheCards == "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"); + Assert.IsTrue(allTheCards == cardNamesInOrder); } [TestMethod] public void ShufflingChangesDeck() { - Deck d1 = new Deck(); - d1.Shuffle(); - string allTheCards = ConcatenateDeck(d1); + // 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 != "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"); + Assert.IsTrue(allTheCards != cardNamesInOrder); } } }