Solution - Mega Challenge War

Tutorial Series: Free C# Fundamentals via ASP.NET Web Apps

Previous Article  |  Next Article


Get GitHub Code

This is the solution for the third "Mega Challenge" called MegaChallengeWar. The goal of this challenge was for you to create an application that simulates the computer playing a game of War. This solution will go into detail on all the steps required to solve this challenge in a particular manner. This is by no means the only solution to the challenge, but hopefully it will help you to understand the thought process behind completing it successfully. Read on as far as you need to in order to understand the problem you're facing, or if you solved it and want to see how it compares, feel free to compare and leave a comment with your solution!

Step 1: Create a New Project and Classes

To begin with, create a new Project called MegaChallengeWar and add to it a Default.apsx page as we've been doing throughout this series. In the Design view, add the following text and server controls to the form, with the matching programmatic ID's:

 

DefaultForm

 

  • playButton
  • resultLabel

That is all we will add to this page for now, as the Default.aspx will dependent on the other classes to provide it the information it needs. It will primarily concern itself with displaying information back to the user. So, let's begin adding the other classes that will generate information and perform the game logic. Begin by right-clicking on your Project in the Solution Explorer and select Add > New Class... then name the new class Game.cs:

 

AddClassDialog

 

Repeat this process to add three more classes:

  • Player.cs
  • Card.cs
  • Deck.cs

Step 2: Working with the Deck

Part 1: Initialize a new Deck

We'll begin working with these classes by first creating the deck, because the cards contained within it are required for every other part of the game. In Deck.cs, initialize a List<Card> called _deck as a private property for the class. Then, create a constructor method for the Deck, initializing the _deck to a new List<Card>:

 

DeckConstructorInitial

 

In order to populate _deck with Cards, we need to populate the Card class with the appropriate properties needed. We'll give it two string properties, one for Suit, and one for Kind. Suit would refer to either Clubs, Spades, Diamonds or Hearts, whereas Kind refers to either the number or Jack, Queen, King or Ace:

 

CardClassInitial

 

Now that we have properties for the cards, let's go back and populate _deck with cards. Now, one way we could do this is by using a Collection Initializer on the _deck, like so:

 

WrongDeckInitializer

 

However, you can see that the downfall of this is that you would need to create an entry for each Card, 52 in total. That would be a lot of typing and clutter, and opens up the possibility for mistyped data. If you choose to do it this way, that's fine. However, for the purposes of this solution, we'll do it in a different manner.

Create two new string arrays called 'suits' and 'kinds'. These array will hold their respective values, suits with the suits and kinds with the kinds:

 

DeckArrays

 

Now, we can use foreach() statements to iterate through the arrays:

 

foreachDeck

 

This will ensure that for each suit, each kind is evaluated, and then a new Card with the current suit and kind is added to _deck. That way, each Suit receives all the cards it needs and they are added to _deck without needing to list every single card.

Part 2: Test the Code

At this point, it would be helpful to make sure that the code we've written for the Deck class works, as the rest of our code will depend on it. It's usually a bad idea to write a lot of code without testing, because if you encounter a bug, you won't know where it's located. So, let's test this code by temporarily making the _deck property public:

 

TestPublicDeck

 

What this will allows us to do is initialize a deck in the Default.aspx.cs when the user presses the playButton. Make sure to generate the playButton_Click by double-clicking the playButton in the Default page. Inside this code block, initialize a new Deck called deck. Then, using a foreach() statement, we'll want to iterate through each card in the _deck property of deck and display it's information to the resultLabel:

 

DeckTestForEach

 

If done successfully, you should see your deck's suits and kinds listen when the playButton is pressed:

 

DeckTestFinal

 

Now that we know the code works, delete the code in the playButton_Click and reset _deck to a private property within the Deck class.

Step 3: Dealing Cards to Players

Now that we have initialized a starting deck, we need to actually deal out the cards to the players. The first player will be dealt a random card, followed by the second player being dealt a random card, then returning to the first and so on, until all the cards in the deck have been dealt. This way, no one player has the advantage or disadvantage in terms of which cards they receive.

Before cards can be dealt to the players, the Player class needs to be populated with properties that can be worked with. Navigate to Player.cs and create the following properties:

 

PlayerProperties

 

Now that the Player is expecting a List<Card> to populate their hand, we can begin to deal cards to them. To do this, return to the Deck class and create a new method called Deal() that takes in two Players:

 

InitialDealMethod

 

Because the process of dealing cards will be done randomly, an instance of Random will need to be created for this class and initialized inside the constructor:

 

DeckRandomInitialize

 

The process of dealing cards to players will consist of three main steps:

 

  1. Use the Random class to select a random card from _deck
  2. Add that random card to the Player's deck
  3. Remove that same card from _deck

 

IncohesiveDeal

 

Notice that, while these steps all pertain to some aspect of dealing cards, performing it all in the Deal() method would result in a loss of cohesion. Deal() shouldn't know how to locate or remove cards from _deck, nor should it be a multi-step process within the method. Deal() should be focused on simply giving cards to each Player it is passed. This is a good indication that the responsibility of retrieving a Card and then removing it should be delegated to a new helper method.

Create a new private method called dealCard() that takes a Player as a parameter:

 

InitialDealCard

 

Now, inside this method we will perform the same basic logic as we would have in the Deal() method, accessing a random member of _deck, assigning it to player, then removing that member of _deck:

 

secondDealCard

 

Finally, call the dealCard() method for each player in the Deal() method:

 

FinalDealMethod

 

Step 4: Displaying the Deal to Screen

Part 1: Working within the Deal() Method

Now that we've created the methods for dealing cards to the players, we need to test them in order to ensure it functions properly. We can do this by displaying the result of the deal to the screen. To do this, create a new private StringBuilder field in the Deck class called _sb and initialize it in the Deck() constructor:

 

stringBuilderField

 

Note: When using the StringBuilder class, you may need to add a using statement for System.Text. You can do this manually, or by pressing Ctrl + '.' when you type in 'StringBuilder', and generate the using statement from there.

Inside the dealCard() method, we'll use this StringBuilder instance to piece together the name of the player, the suit and kind of the card they were dealt and text to format it for display:

 

dealCardFinal

 

Because _sb is a private field within this class, we can reference it directly from the Deal() method located in the same class. Change the return type of Deal() from void to string, so that it will return the StringBuilder text to the caller after the while() loop is completed:

 

DealSB

 

Part 2: Calling the Deal() Method

This will work properly, but there is no call to the Deal() method that this string can be returned to. The caller to this method should be the same caller that is responsible for coordinating the calls to the other main methods within our game. This is the explicit purpose of the Game class, to act as a manager for all the elements of the game.

Before we can do this, the Game class needs to be populated with the properties it needs in order to function. Navigate to Game.cs to create private fields and create a constructor for the class with the following code to initialize the players:

 

GameConstructor

 

Next, create a public method called Play() that initializes an instance of Deck and calls the deck.Deal(), passing in the two players:

 

PlayMethod

 

Next, navigate to Player.cs to create a constructor for Player that initializes the Player's cards. This will allow us to retrieve that List<Card> representing each player's hand whenever the Play() method is called:

 

PlayerConstructor

 

Return to the playButton_Click in Default.aspx.cs. Write the following code to call the Play() method and display the results to screen:

 

playButtonDealTest

 

Save and run your project to test and see the results:

 

PlayWarDealTest

 

Step 5: Assign Numeric Value to Cards

Now that each player has a hand of cards that they can battle with, we can begin to fill out the actual battle logic. The battle will be handled in three basic steps:

  1. Both players compare cards
  2. The higher card (Ace-high) wins the battle
  3. The winner adds both cards to their deck

We will tackle these steps individually to ensure that our concerns are separated effectively. To begin with, we'll need to make a way to determine the value of each card being compared in within battle. For comparison's sake, we'll assign a numeric value to each card that corresponds to its rank in the deck.

In the Card class, create a new public int method called CardValue() and write in the following code to determine each Card's given value:

 

CardValueMethod

 

This code might look a bit confusing at this point, but we'll talk about the switch() statement in more detail in a later lesson. This essentially works as several compounded if() statements. It begins by checking if the current Card's Kind is equal to any of the given string values, then assigning it the appropriate numeric value. If none of the cases apply, it defaults to a TryParse() to convert the string value (i.e. "2") to an integer (literal 2). Finally, the method returns the integer value to the caller.

Step 6: Performing the Battle

Return to the Game class' Play() method, and create a while() loop that ensures that neither player has exhausted their cards:

 

playMethodWhile

 

The next step is to determine which card has a greater value. We'll do this by creating an if() statement to determine if player1's card value is greater than player2's by calling the CardValue() method:

 

compareCards

 

The player who wins will take both cards being evaluated and add them to the bottom of their deck. However, this concern is separate from the Play() method and should comprise a method of its own. Create the following private method that returns type Card:

 

initialGetCard

 

This removes the provided player's card from their hand, but does not yet add the card to the winning player's deck. Because there will be two separate calls to this method (one for each player), we will need to create a List<Card> that will hold the retrieved card from each method call. Create a new private field of type List<Card> to hold the bounty of cards that will be added to the winning player's deck and initialize it within the constructor:

 

bountyInitialize

 

Return to getCard() and add the card to the _bounty List:

 

bountyGetCard

 

Returning to the while() loop within the Play() method, rewrite the player1Card and player2Card variables, setting them each equal to the returned value of the getCard() method:

 

playerCardGetCard

 

Instead of housing the comparison logic within the Play() method (which should be coordinating method calls rather than performing logic), we'll create a new private method called performEvaluation(). Move the evaluation code into the method, so that it looks like this:

 

initialPerformEvaluation

 

If player1's card value is greater than player2's, we'll add the _bounty to their deck, or else if player2's card value is greater, they will receive the bounty.

Note: The else if() is used here instead of else, because the else() will be used later on to account for a tie leading to a War scenario.

Return to the Play() method and create a string result variable, assigned to the value of deck.Deal(). This will allow the method to continue on to the while() loop instead of returning out of the method at that point:

 

stringResultPlayMethod

 

Next, call performEvaluation() inside the while() loop, passing in the players and their cards. Finally, we need to satisfy one of the requirements for this challenge, to terminate the game after 20 rounds of play. To do this, we'll create a new integer variable called round and increment it at the end of each loop. Then, create an if() statement to break out of the loop if round is greater than 20:

 

roundBreak

 

Finally, within the performEvaluation() method, use the Clear() method to remove all the cards from _bounty after they've been assigned to a Player:

 

performEvaluationBountyClear

 

Step 7: Displaying the Winner

Now that the comparison has been performed, and we've broken out of the battle loop, we need to make a way to display the winner to the screen. Create a new private method called determineWinner() that evaluates which player ended up with the greater number of cards:

 

initialDetermineWinner

 

We'll fill in the logic, adding to result based upon which player won, then also adding both player's card count and returning the result to the caller:

 

determineWinnerFinal

 

Call determineWinner() beneath the while() loop in the Play() method:

 

PlayReturnResult

 

Save and run your project to see the result:

 

BattleResultTest

 

Step 8: Refactoring the Code

If you review the code we've written so far, specifically in the Game class, it becomes apparent that it needs to be refactored and cleaned up. The methods and responsibilities of the Game class have expanded beyond what they should. Not only is it responsible for coordinating the play of the Game, it houses all the logic for the battles themselves. In order to separate our concerns out and keep our classes cohesive, let's create a new class called Battle that will be responsible for the battle logic. Once created, begin by moving the code for _bounty over to the new Class, making sure to remove that code from Game.cs:

 

BattleBountyField

 

Next, transfer the getCard() and performEvaluation() methods into the new Battle class as well:

 

BattleLogicMethods

 

Next, create a new public method within Battle called PerformBattle(), using the following code from the Play() method:

 

performBattleInitial

 

In order to access the PerformBattle method in the Game class, a new instance of Battle will need to be initialized. Return to the Play() method's while() loop to create a new instance of Battle, and call its PerformBattle() method, passing in the required players:

 

whilePerformBattle

 

Step 9: The War Scenario

You may notice when testing your code that sometimes the result doesn't include all of the cards in the deck. The reason for this is found in the performEvaluation() method. We created if() and else if() statements in the event that the cards had unequal values, but never created a case for a tie. We're going to evaluate that case now, as we return to the Battle class to create a new helper method to deal with this war scenario.

In Battle.cs, create a new private method called war(), passing in both player1 and player2:

 

initialWarMethod

 

A War will play out in the following manner:

 

  1. Each player adds the next three cards from their deck to the bounty
  2. A battle takes place, evaluating the second card from each player
  3. If there is a winner, they are awarded all eight cards from the bounty

 

We'll begin by adding the three cards for player1 into _bounty by calling the getCard() method three times. The second card, however, will be assigned to a new variable called warCard1:

 

warMethodGetCardPlayer1

 

Repeat this process for player2's cards as well, so that all the cards are added to the bounty. After that process is complete, call performEvaluation() within this method, passing in both players and both warCards, so that the new cards perform a battle:

 

warMethodPerformEvaluation

 

Now, call the war() method from within performEvaluation() under the else() clause:

 

performEvaluationWar

 

Step 10: displayBattleCards() Method

We've now successfully implemented the logic for the war() method and called it, but we have no way of ensuring that it functions properly. We need to display to the screen that the war has taken place, which cards are being compared, and which are in the bounty.

To do this, we'll create two new helper methods in the Battle class: displayBattleCards() and displayBountyCards():

 

displayWarCards

 

Because it is evaluating the two cards being compared in the war, displayBattleCards() has an input parameter expectation for two cards. In contrast, the displayBountyCards() method will look at the private field _bounty, so it doesn't need any input parameters.

In order to actually format the text for display to the user, create a private StringBuilder field within the Battle class and name it _sb. Then, initialize this field in the constructor:

 

BattleSBInitialize

 

Next, return to displayBattleCards() and type in the following code to append the information to the StringBuilder:

 

displayBattleCards

 

The displayBountyCards() method will implement the same logic, but in a different way because we won't know at the onset how many cards are contained within _bounty. What we can do is iterate through the List<Card> and print out a result for each card in the collection:

 

displayBountyCards

 

Step 11: Refactor performEvaluation()

Now that we have a way to display both the evaluation and results of the battle, we need to call the appropriate methods from performEvaluation(). However, when you look at performEvalution(), notice how many responsibilities it is manages after calling these methods:

 

refactorPerformEvaluation

 

This method is not only performing the evaluation of cards and determining the winner, but it's displaying the cards, managing _bounty, adding to it and divvying it out. At the very least, the process of managing _bounty should be handled in a different helper method. Within Battle.cs, create a new private void awardWinner() method with the following code transferred from performEvaluation():

 

awardWinnerInitial

 

Now make the following changes to the performEvaluation() method to call awardWinner():

 

performEvaluationAwardWinner

 

Return to the awardWinner() method and modify it in order to append the winning player's name to the StringBuilder:

 

awardWinnerSB

 

Save and run your project to see the result at this point:

 

projectTestPostWar

 

Step 12: Finalizing the Formatting

Now that our entire game logic is firmly in place, the final step is to go through and finish up the formatting for the game, making it easily readable to the user. We'll begin in the Play() method of the Game class by creating headers for when dealing cards and when the battle begins:

 

PlayMethodHeaders

 

We'll also need to indicate to the user that a war scenario is playing out. To do this, return to the war() method in Battle.cs and append the following to the StringBuilder to identify a war:

 

warIdentifier

 

Save and run your project to see the result:

 

playWarHeader1

 

 

playWarHeader2

 

 

warScenario

 

Conclusion:

This completes the solution for MegaChallengeWar. This is simply one of many possible ways to complete the challenge, and it is certainly far from the best. While there are many better ways to handle displaying the results to the user, we have not yet covered them, which meant we had to stitch a lot together. Sometimes the process can be messy when getting a particular result, but it does work. As always, if you solved the challenge in a different way, that is perfectly acceptable. If you didn't complete the challenge, try again and reinforce these concepts in your mind. A lot of this process is trial and error, refactoring and modifying. Coding isn't like chiseling a statue out of stone; it's akin to molding a statue from clay. It changes and evolves throughout the process of creating it. And that's good. It gets better, more precise and more cohesive through that process. Good job on completing this challenge, keep it up!


Related Articles in this Tutorial:

Lesson 1 - Series Introduction

Lesson 2 - Installing Visual Studio 2015

Lesson 3 - Building Your First Web App

Lesson 4 - Understanding What You Just Did

Lesson 5 - Working with Projects in Visual Studio

Lesson 6 - Simple Web Page Formatting in Visual Studio

Challenge 1

Solution 1

Lesson 7 - Variables and Data Types

Lesson 8 - Data Type Conversion

Lesson 9 - Arithmetic Operators

Lesson 10 - C# Syntax Basics

Challenge 2 - ChallengeSimpleCalculator

Solution - ChallengeSimpleCalculator

Lesson 11 - Conditional If Statements

Lesson 12 - The Conditional Ternary Operator

Challenge 3 - ChallengeConditionalRadioButton

Solution - Challenge Conditional RadioButton

Lesson 13 - Comparison and Logical Operators

Lesson 13 Challenge - First Papa Bob's Website

Solution - Challenge First Papa Bob's Website

Lesson 14 - Working with Dates and Times

Lesson 15 - Working With Spans of Time

Lesson 16 - Working with the Calendar Server Control

Challenge 4 - Challenge Days Between Dates

Solution - Challenge Days Between Dates

Lesson 17 - Page_Load and Page.IsPostBack

Lesson 18 - Setting a Break Point and Debugging

Lesson 19 - Formatting Strings

Challenge 5 - Challenge Epic Spies Assignment

Solution - Challenge Epic Spies Assignment

Lesson 20 - Maintaining State with ViewState

Lesson 21 - Storing Values in Arrays

Lesson 22 - Understanding Multidimensional Arrays

Lesson 23 - Changing the Length of an Array

Challenge 6 - Challenge Epic Spies Asset Tracker

Solution - Challenge Epic Spies Asset Tracker

Lesson 24 - Understanding Variable Scope

Lesson 25 - Code Blocks and Nested If Statements

Lesson 26 - Looping with the For Iteration Statement

Challenge 7 - Challenge For Xmen Battle Count

Solution - Challenge For Xmen Battle Count

Lesson 27 - Looping with the while() & do...while() Iteration Statements

Lesson 28 - Creating and Calling Simple Helper Methods

Lesson 29 - Creating Methods with Input Parameters

Lesson 30 - Returning Values from Methods

Lesson 31 - Creating Overloaded Methods

Lesson 32 - Creating Optional Parameters

Lesson 33 - Creating Names Parameters

Lesson 34 - Creating Methods with Output Parameters

Challenge 8 - Challenge Postal Calculator Helper Methods

Solution - Challenge Postal Calculator Helper Methods

Mega Challenge Casino

Solution - Mega Challenge Casino

Lesson 35 - Manipulating Strings

Challenge 9 - Phun With Strings

Solution - Challenge Phun With Strings

Lesson 36 - Introduction to Classes and Objects

Challenge - Hero Monster Classes Part 1

Solution - Hero Monster Classes Part 1

Challenge - Hero Monster Classes Part 2

Solution - Challenge Hero Monster Classes Part 2

Lesson 37 - Creating Class Files Creating Cohesive Classes and Code Navigation

Lesson 38 - Understanding Object References and Object Lifetime

Lesson 39 - Understanding the .NET Framework and Compilation

Lesson 40 - Namespaces and Using Directives

Lesson 41 - Creating Class Libraries and Adding References to Assemblies

Lesson 42 - Accessibility Modifiers, Fields and Properties

Lesson 43 - Creating Constructor Methods

Lesson 44 - Naming Conventions for Identifiers

Lesson 45 - Static vs Instance Members

Challenge 10 - Challenge Simple Darts

Solution - Challenge Simple Darts

Lesson 46 - Working with the List Collection

Lesson 47 - Object Initializers

Lesson 48 - Collection Initializers

Lesson 49 - Working with the Dictionary Collection

Lesson 50 - Looping with the foreach Iteration Statement

Lesson 51 - Implicitly-Typed Variables with the var Keyword

Challenge 11 - Challenge Student Courses

Solution - Challenge Student Courses

Mega Challenge War

Solution - Mega Challenge War

Lesson 52 - Creating GUIDs

Lesson 53 - Working with Enumerations

Lesson 54 - Understanding the switch() Statement

Lesson 55 - First Pass at the Separation of Concerns Principle

Lesson 56 - Understanding Exception Handling

Lesson 57 - Understanding Global Exception Handling

Lesson 58 - Understanding Custom Exceptions

Lesson 59 - Creating a Database in Visual Studio

Lesson 60 - Creating an Entity Data Model

Lesson 61 - Displaying the DbSet Result in an ASP.NET GridView

Lesson 62 - Implementing a Button Command in a GridView

Lesson 63 - Using a Tools-Centric Approach to Building a Database Application

Lesson 64 - Using a Maintenance-Driven Approach to Building a Database Application

Lesson 65 - Creating a New Instance of an Entity and Persisting it to the Database

Lesson 66 - Package Management with NuGet

Lesson 67 - NuGet No-Commit Workflow

Lesson 68 - Introduction the Twitter Bootstrap CSS Framework

Lesson 69 - Mapping Enum Types to Entity Properties in the Framework Designer

Lesson 70 - Deploying the App to Microsoft Azure Web Services Web Apps

Papa Bob's Mega Challenge

Papa Bob's Mega Solution Part 1 - Setting up the Solution

Papa Bob's Mega Solution Part 2 - Adding an Order to the Database

Papa Bob's Mega Solution Part 3 - Passing an Order from the Presentation Layer

Papa Bob's Mega Solution Part 4 - Creating the Order Form

Papa Bob's Mega Solution Part 5 - Adding Enums

Papa Bob's Mega Solution Part 6 - Creating an Order with Validation

Papa Bob's Mega Solution Part 7 - Calculating the Order Price

Papa Bob's Mega Solution Part 8 - Displaying the Price to the User

Papa Bob's Mega Solution Part 9 - Creating the Order Management Page


Comments

Please login or register to add a comment