Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

C# ПІДРУЧНИКИ / c# / Hungry Minds - ASP.NET Bible VB.NET & C#

.pdf
Скачиваний:
128
Добавлен:
12.02.2016
Размер:
7.64 Mб
Скачать

A hand in the participant is worth . . .

If the Participant class were something I felt I could generalize to use in other games as well, I could create a DLL for it and import it as I did with the PlayingCards namespace. But for now, I've just included it within the page itself, as shown here:

Class Participant

Public Hand As ArrayList

Sub New()

Hand = New ArrayList

End Sub

The most important part of this page is the Public Hand ArrayList. It is declared and then, in the constructor for the class, instantiated.

Once the Participant-type object is created, Hand can be freely accessed and manipulated using all the standard ArrayList methods. No attempt was made here (as there was with Shoe) to hide this structure and control access to it. This approach makes it easy to implement and intuitive to use, but also limits the control you'll have over this structure. In fact, you'll have no control over this structure. But, for this use, I decided that was okay — especially because I am creating it on this page for the exclusive use in this game. If you do generalize this class in the future for other games, you definitely want to revisit the question of making Hand public.

The two methods in this class provide functionality to support the Hand ArrayList, which is assumed to contain Card-type objects.

Adding up the TotalValue

The first of these methods, TotalValue, simply loops through all the cards in the Hand, sums up their value, and returns the result:

Function TotalValue As Integer

Dim i As Integer

Dim AceFlag As Boolean = False

Dim CurCard As Card

TotalValue = 0

For Each CurCard In Hand

TotalValue = TotalValue + CurCard.Value

If CurCard.Name = "Ace" Then

AceFlag = True

End If

Next

If AceFlag = True And TotalValue + 10 < 21 Then

TotalValue += 10

End If

End Function

Notice that this is the code that handles the ace's possible alternate values. The rule is that an ace can count as either 1 or 11 at the participant's option. But in reality, if you

have two aces and count them both as 11, your total is a busting 22. So you'll only ever count, at most, one ace as 11. And, even then, you'll only do it if it doesn't cause you to bust.

With that in mind, this code adds up the total of all the cards in the hand, setting a flag to true if at least one of them is an ace. The aces are, by default, counted as 1, so the code after the loop checks to see if adding an additional 10 to the total (turning one of the aces into 11 instead of 1) causes the participant to bust. If not, 10 is added to the total.

Some user interfacing: UpdateLabels

The UpdateLabels method takes the internal data structures and shows them to the user using the labels on the page.

Typically, when you create business objects, you make a strict line of division between the user interface and the business rules. In the case of the UpdateLabels method, I cheated. I'll explain why after I describe how it works.

Sub UpdateLabels(CardLabels As ArrayList, TotalLabel As Label)

Dim i As Integer

Dim CurCard As Card

Dim CurLabel As Label

For i=0 To CardLabels.Count-1

CurLabel = CType(CardLabels(i), Label)

If i <= Hand.Count-1 Then

CurCard = CType(Hand(i),Card)

CurLabel.Text = CurCard.Name & " of " & CurCard.Suit

Else

CurLabel.Text = ""

End If

Next

TotalLabel.Text = CStr(TotalValue)

End Sub

End Class

UpdateLabels accepts an ArrayList of label controls and a single label control. It then goes through all the labels in the ArrayList and fills them in with the name of the cards in this participant's Hand. Any labels that don't have corresponding cards in the Hand are set to the empty string to clear out any previous values that might be there.

Then, to top it off, UpdateLabels calls the TotalValue method and plots that information into the label sent in the second argument to this method.

This method is clearly designed to update the user interface with the current values stored in the business object. And, typically, that would disqualify it from being a part of the business object. But, in my mind, three mitigating factors justified making it a method:

§If I didn't include the subroutine in the class, I'd have two other options: I'd have to create two nearly identical subroutines on the page to update the UI for the dealer and another to update the UI for the player. That's no good. The other option is to generalize the function on the page to accept a Participant object in addition to the two other arguments and then use it to fill in the labels. That would work, but I felt this method was simpler and more straightforward.

§This class is a part of this page, rather than included in a DLL that may be used on other pages (with different user interface needs). This means that its scope and use is limited to begin with. In this case, the Participant class was created more for code organization and readability than for reuse. Therefore, the issue of blurring the line between business object and user interface is less significant.

§The method doesn't directly access the user interface. I wrote it so that it would work with any ArrayList of labels and any individual label. This means that although the method technically does a user interface task, it is not directly tied to a specific user interface implementation.

Whether you agree with the decision or not, that's the way I did it. If it bothers you, it would be relatively easy to pull the subroutine out of the class, add the Participant-type object as another argument, and tweak the code to use the Hand in the argument.

The body

I'm going to go a little out of order here. I think that when you are trying to figure out someone else's code, you should look at three things, in this order: the classes they create and use, the body of the page, and the code written to respond to page events.

The key classes, Shoe and Participant, are discussed in previous sections of this chapter. Before I dive into the event code, it's important that you take a look at the body of the page so that you can see the user interface and the controls that are likely to trigger events:

<body>

<h1>Blackjack</h1>

<p>Your goal is to try to get closer to 21 than the

dealer without going over. As you begin you have two cards

and you get to see the dealer's first card. Face cards count

as 10 and Aces can count as either 1 or 11 (whatever works

best for you). You can click Hit to draw another card or

click Stand to keep your current total.</p>

<form action="blackjack.aspx" method="post" runat="server">

<table width=100%>

<tr><td align="left" valign="top">

<h2>Your Hand</h2>

Card 1: <asp:label id="PCard1" runat="server"/><br>

Card 2: <asp:label id="PCard2" runat="server"/><br>

Card 3: <asp:label id="PCard3" runat="server"/><br>

Card 4: <asp:label id="PCard4" runat="server"/><br>

Card 5: <asp:label id="PCard5" runat="server"/><br>

Card 6: <asp:label id="PCard6" runat="server"/><br>

<br>

Your Total: <asp:label id="PTotal" runat="server"/><br><br>

<asp:button id="Hit" type=submit text=" Hit "

OnClick="Hit_Click" runat="server"/>

 

<asp:button id="Stand" type=submit text="Stand" OnClick=

"Stand_Click" runat="server"/>

</td>

<td align="left" valign="top">

<h2>Dealer Hand</h2>

Card 1: <asp:label id="DCard1" runat="server"/><br>

Card 2: <asp:label id="DCard2" runat="server"/><br>

Card 3: <asp:label id="DCard3" runat="server"/><br>

Card 4: <asp:label id="DCard4" runat="server"/><br>

Card 5: <asp:label id="DCard5" runat="server"/><br>

Card 6: <asp:label id="DCard6" runat="server"/><br>

<br>

Dealer Total: <asp:label id="DTotal" runat="server"/><br>

</td>

</tr>

</table>

<br>

<asp:label id="Message" runat="server"/>

<br><br>

<asp:label id="PlayAgain" runat="server" text="Play Again?" visible=false />

  

<asp:button id="Yes" type=submit text="Yes" OnClick="Yes_Click"

runat="server" visible=false /> 

<asp:button id="No" type=submit text=" No " OnClick="No_Click"

runat="server" visible=false />

</form>

</center>

</body>

After displaying the header and some instructions, a table is used to organize all the pieces on this page. The table essentially divides the page into two sections vertically. On the left is the player's side, which contains labels to show the player cards and a total. In addition, buttons are included so that the player can indicate their preference to Hit or Stand. On the right is the dealer's side, and its controls are identical, with the omission of the Hit/Stand buttons.

Below the table is a label called Message, which is used to tell the user the final result of the game — whether they won or lost.

Finally, there are three controls that are initially hidden and only made visible (in code) when the game is complete. A label asks the user if they'd like to play again, and the Yes and No buttons allow the user to respond.

Initialization and object juggling

The first three subroutines on this page are designed to initialize, store, and retrieve important information that is used from one server round trip to the next. The first two subroutines are events that are triggered in the life cycle of processing the page:

Page_Load and Page_PreRender. The third is a subroutine called from the

Page_Load event: StartGame.

Creating an array of labels

The Page_Load in this page is to the hands of the

event is used to initialize variables and instantiate objects. The first task create an ArrayList to hold the label controls that display the cards in player and dealer on the page:

Sub Page_Load(sender As Object, e As EventArgs)

PlayerLabels = New ArrayList

DealerLabels = New ArrayList

PlayerLabels.Add(PCard1)

PlayerLabels.Add(PCard2)

PlayerLabels.Add(PCard3)

PlayerLabels.Add(PCard4)

PlayerLabels.Add(PCard5)

PlayerLabels.Add(PCard6)

DealerLabels.Add(DCard1)

DealerLabels.Add(DCard2)

DealerLabels.Add(DCard3)

DealerLabels.Add(DCard4)

DealerLabels.Add(DCard5)

DealerLabels.Add(DCard6)

Most of the time, when you work with controls on a page, you simply use their name and access their properties and methods. But sometimes it is preferable to work with a set of controls in a different way.

In the section titled " The body," previously in this chapter, I pointed out that this page has six labels to show the cards of the player, and another six labels to show the cards of the dealer. And, in a section called "Some user interfacing: UpdateLabels," earlier in this chapter, I described the UpdateLabels method of the Participant class. This method updates the labels on the page with the names of the cards that are in the Participant's hand. The only catch is that this method requires that the user send the labels in the form of an ArrayList. That simplifies the updating of those labels because a loop can be used, along with an index number, to walk through each one. Here, in the Page_Load event, is where that ArrayList of labels, for both the player and the dealer, is created.

The PlayerLabels and DealerLabels ArrayLists are instantiated and promptly filled with the controls on the page, in the appropriate order. This happens the first time the page loads and on every round-trip thereafter.

Creating and initializing GameShoe, Player, and Dealer

The next piece of code, a common site in the Page_Load event, is an If...Then statement that checks Not IsPostBack. This Page property determines whether this is the first time the page has been retrieved or not.

If Not IsPostBack Then

StartGame

In this case, if this is the first time the page has been retrieved, the StartGame subroutine is called. Here, all the objects needed for the game are instantiated and all the variables initialized:

Sub StartGame

GameShoe = Nothing

Player = Nothing

Dealer = Nothing

GameShoe = New Shoe(1)

GameShoe.Shuffle

Player = New Participant

Dealer = New Participant

Player.Hand.Add(GameShoe.Draw)

Player.Hand.Add(GameShoe.Draw)

Dealer.Hand.Add(GameShoe.Draw)

Player.UpdateLabels(PlayerLabels, PTotal)

Dealer.UpdateLabels(DealerLabels, DTotal)

Message.Text = ""

End Sub

This subroutine is called from the Page_Load event and then again, later, if the user wants to start over and play a new game. So, in case the GameShoe, Player, and Dealer variables already contain objects, they are set to Nothing to clear them out. Then the GameShoe is instantiated as a new Shoe class. A 1 is passed to the constructor, indicating that a one-deck shoe should be created. Then the cards in the shoe are shuffled.

Player and Dealer are each instantiated as Participants.

Next the GameShoe's Draw method is called three times and the result (a Card object) is added to the hands of the player and dealer.

UpdateLabels is called separately for each participant passing the appropriate ArrayList and the label control holding the card totals. This causes the labels on the page to be updated, showing the two cards in the player's hand and the single card in the dealer's hand.

Storing and retrieving the objects from Session variables

Here's a timeline of the events that will happen on this page:

1. The page is requested and loaded for the first time. The Page_Load event occurs and the StartGame subroutine is called. This subroutine creates and instantiates the GameShoe, Player, and Dealer objects, preparing them to be ready for play.

2. The Page_PreRender event occurs. This event happens after all the server-side code has executed and just before the final HTML is sent to the browser. It's the last opportunity to do anything. I use this event to store my key objects in Session variables.

3. The first server round trip occurs. The Page_Load event is executed. The objects are retrieved from their Session variables immediately so that they'll be available to any other events that occur.

4. After all events are done, the Page_PreRender event occurs again. And again, the objects are stored away in Session variables for safekeeping until the next server round trip.

The StartGame subroutine was discussed in the previous section. The code in the Page_PreRender event looks like this:

Sub Page_PreRender(sender As Object, e As EventArgs)

Session("GameShoe") = GameShoe

Session("Player") = Player

Session("Dealer") = Dealer

End Sub

The objects are placed in Session variables and stored in the server's memory until the page needs them again.

Then, in the Page_Load event, if this is a post-back, this code executes:

Else

GameShoe = CType(Session("GameShoe"),Shoe)

Player = CType(Session("Player"),Participant)

Dealer = CType(Session("Dealer"),Participant)

End If

This code does just the opposite of the code in the Page_PreRender event — it gets the objects out of the Session variables. Because Session variables are not typed, a CType command is used to assure the proper conversion back into their associated classes.

This approach, using Page_Load and Page_PreRender to store and retrieve commonly used global variables and objects, is very handy and can be applied to any page that needs to remember things from one round-trip to the next. If you only need to store variable values, then you can use ViewState instead of Session and avoid taking up precious server memory. Unfortunately, you can't store objects in ViewState unless they implement the ISerializable interface.

Responding to events

All of the work thus far has been in an effort to get to this point: a blackjack table presented to the user and ready for them to play the game.

Now the ball is in the user's court. They look at the cards they were dealt, look at the dealer's first card, and make a decision: Hit or Stand.

Hit me

If the user has a low total, or is feeling lucky, the user is likely to click the Hit button. When the user does this, the following subroutine is executed:

Sub Hit_Click(Sender As Object, E As EventArgs)

Player.Hand.Add(GameShoe.Draw)

Player.UpdateLabels(PlayerLabels, PTotal)

If Player.TotalValue = 21 Then

Message.Text = "You Have 21...You Win!"

EndGame

ElseIf Player.TotalValue > 21

Message.Text = "You Busted...Dealer Wins!"

EndGame

End If

End Sub

A card is drawn from the GameShoe and added to the player's hand. The labels on the page are updated. That's all there is to a hit!

However, after the user has a new card, there are some conditions to check. For example, does the player have 21? If so, the player wins and the game is over. Conversely, if this draw put the user over the edge and the user's total is greater than 21, then the player has busted and therefore loses.

I believe I'll stand

After the user achieves a good total, they'll click the Stand button. That event triggers this subroutine:

Sub Stand_Click(Sender As Object, E As EventArgs)

Message. Text = "You decided to stand on " & _

CStr(Player.TotalValue)

DealerPlay

End Sub

The Message label is updated to indicate the user's decision to stand, and the value they chose to stand on. Because clicking Stand represents the end of the player's turn, the DealerPlay subroutine is called from here.

The dealer's turn

If the player gets exactly 21 or busts, the result is a forgone conclusion and there's no reason for the dealer to even play. The dealer only goes when the player decides to stand on a particular total. This is the subroutine that gets called when that happens:

Sub DealerPlay

Dealer.Hand.Add(GameShoe.Draw)

Do While Dealer.TotalValue < 17

Dealer.Hand.Add(GameShoe.Draw)

Loop

Dealer.UpdateLabels(DealerLabels, DTotal)

DetermineWinner

EndGame

End Sub

A card is drawn from the GameShoe and added to the dealer's hand. Next, a Do While loop checks to see if the total is less than 17. If it is, the dealer keeps on drawing. This is the standard strategy that dealers are required to follow in casinos.

Once this loop completes, the labels on the page are updated to show the dealer's new cards.

The DetermineWinner subroutine is called to do just that. After the DetermineWinner subroutine executes, the EndGame subroutine is called.

Who won?

The player chose to stand and the dealer did its drawing. So what's the result? The DetermineWinner subroutine does the comparisons to see who won and displays the result in the Message label:

Sub DetermineWinner

If Dealer.TotalValue = Player.TotalValue Then

Message.Text = "A Push! No Winner..."

ElseIf Dealer.TotalValue = 21 Then

Message.Text = "Dealer Has 21...Dealer Wins!"

ElseIf Dealer.TotalValue > 21 Then

Message.Text = "Dealer Busted...You Win!"

ElseIf Dealer.TotalValue > Player.TotalValue Then

Message.Text = "Dealer Wins..."

Else

Message.Text = "You Win! Congratulations!"

End If

End Sub

This code doesn't need to check for a player 21 or a player bust because those conditions were already handled in the Hit_Click subroutine. So here the only options are push, dealer with 21, player with 21, dealer wins, and player wins.

Ending the game

No matter who wins or loses, the game always ends in the same way:

Sub EndGame

Hit.Enabled = False

Stand.Enabled = False

PlayAgain.visible=True

Yes.visible= True

No.Visible=True

End Sub

The Hit and Stand buttons can't be used now, so they are disabled. However, the PlayAgain label along with the Yes and No buttons are made visible.

The label asks the user if they'd like to play again. If the answer is no, No_Click is called:

Sub No_Click(Sender As Object, E As EventArgs)

Yes.visible = False

No.visible = False

PlayAgain.text = "Thanks For Playing!"

End Sub

The buttons are made invisible again and the PlayAgain text is changed to thank the user for playing.

If the answer to PlayAgain is Yes, Yes_Click is executed:

Sub Yes_Click(Sender As Object, E As EventArgs)

Yes.visible = False

No.visible = False

PlayAgain.visible = False

Hit.Enabled = True

Stand.Enabled = True

StartGame

End Sub

All the originally invisible controls become invisible again. Hit and Stand are enabled and the StartGame subroutine is called to kick off the process once again.

Room to Grow

This chapter is a running start, but you certainly have lots of opportunities to take this project and make it your own. Here are some ideas for enhancing it:

§As already mentioned, the user interface is, well, simple. You could easily add some pizzazz by using either graphics or HTML tables to emulate the look of cards on a blackjack table.

§As for the game itself, it would be significantly more fun if you could track how you're doing on an ongoing basis. You could do that by showing wins and losses, but probably the better way to go to simulate a real casino is to give

the user a certain amount of play money when they start and allow them to choose how much of that money they want to gamble on each hand. If they wager $10 and win, they get their original amount back plus an additional $10. You could store the user's current cash in a Session variable.

§After you implement wagering, you could add some rules to give the user more options in the game. Doubling down is an option the player has only once per hand — after being dealt the first two cards. When the player doubles down, they double their bet with the understanding that they'll receive one additional card and then stand on whatever that total is. For example, if the player is dealt a 6 and a 5, for a total of 11 with their first two cards, it is a good idea to double down and hope that the next card is a face card. This allows the player to win twice as much as they would otherwise. Of course, if the next card is a 3, the player cannot hit and is forced to stand on a very weak 14.

§Splitting is another popular option that is also available only after the first two cards are dealt. In addition, you can only split if the first two cards are the same, say two 8s. When you split, you put a sum of money equal to your original bet on the table beside your original bet. The two cards are split up and form the foundation for two separate hands. The player then chooses to hit (as many times as they like) or stand, as usual, on each hand individually, trying to get close to 21 with each hand. Remember that when you split, both hands are playing against the dealer. It's possible to win both hands, lose both, or win one and lose the other.

Summary

Blackjack is a game with just enough complexity to make a coding project interesting. For this project, I used it to demonstrate many different techniques, such as how to create a dynamic user interface with Web forms and user controls and how to use an ArrayList to create a more flexible version of the stack control. Next, I covered how to create a reusable data structure and a reusable component that encapsulates all the complexity for a particular task (in this case, initializing and handling decks of playing cards). This cleans up and simplifies the code in the main page to focus on its task — the game. Then I compiled simple Visual Basic components with the command line compiler and used them within an ASP.NET page. In addition, I used classes within a page to simplify and organize the code (as I did here with Participant) and the Mod operator to process certain iterations within a loop.

Соседние файлы в папке c#