
C# ПІДРУЧНИКИ / c# / Hungry Minds - ASP.NET Bible VB.NET & C#
.pdfRandStack-ing the place
The private variable CardDeck holds an instance of the RandStack class and is created to hold the cards in the Shoe:
Private CardDeck As RandStack = New RandStack
The new constructor
The first and most important method of this class is one that no one will call directly. It's the constructor for the class. It is called when the class is instantiated. In Visual Basic, the constructor for a class is the subroutine that has the name New:
Sub New(NumDecks As Integer)
This constructor accepts one argument: the number of decks that should be created inside this shoe. The blackjack game page will call this constructor with 1, because I wrote it so that each new game starts off with a new, freshly shuffled deck. But it didn't have to be implemented that way. To make it more Vegas -like, you could pass a 7 in the constructor to create a seven-deck shoe, and then keep dealing one game after another out of that shoe until you get about two-thirds of the way through it and then reshuffle. If you were going to create a page that taught people how to count cards, for example, you might want to add this additional level of realism.
The other reason to offer the capability of a multideck shoe, once again, is for reusability. It makes the class much more flexible and adaptable to different applications without really adding much code at all.
Making cards
The entire contents of this subroutine are contained within a loop. This loop iterates once for each card that is to be added to the deck. It loops from 0 to 51 for a one-deck shoe, 0 to 103 for a two-deck shoe, and so on.
For CardIndex = 0 To (52 * NumDecks -1)
Dim NewCard As Card = New Card
Each time through the loop, the second line creates a NewCard object from the Card structure. The constructor then fills that NewCard object with the appropriate information.
Mod makes it easy
I use the Mod operator to make this procedure as simple as possible. For those of you who slept through algebra, I'll briefly describe how Mod works. Mod does long division, just like you did in elementary school. But instead of giving you the answer, it gives you the remainder. So, for example, 11 divided by 5 is 2 with a remainder of 1. So 11 Mod 5 is 1.
Mod is a handy operator to use when you want to do something to every third element in an array or on every seventh iteration of a loop. And that's exactly what you want to do here:
Select Case (CardIndex Mod 4)
Case 0
NewCard.Suit = "Hearts"
Case 1
NewCard.Suit = "Diamonds"
Case 2
NewCard.Suit = "Clubs"
Case 3
NewCard.Suit = "Spades"
End Select
This Select Case statement uses the CardIndex Mod 4, which means that, because the loop begins at 0, the first card is hearts, the second card is diamonds, the fifth card is hearts again, and so on.
The same approach can be taken for the card Name and Value:
Select Case (CardIndex Mod 13)
Case 0
NewCard.Name = "Ace"
NewCard.Value = 1
Case 1
NewCard.Name = "2"
NewCard.Value = 2
Case 2
NewCard.Name = "3"
NewCard.Value = 3
Case 3
NewCard.Name = "4"
NewCard.Value = 4
Case 4
NewCard.Name = "5"
NewCard.Value = 5
Case 5
NewCard.Name = "6"
NewCard.Value = 6
Case 6
NewCard.Name = "7"
NewCard.Value = 7
Case 7
NewCard.Name = "8"
NewCard.Value = 8
Case 8
NewCard.Name = "9"
NewCard.Value = 9
Case 9
NewCard.Name = "10"
NewCard.Value = 10
Case 10
NewCard.Name = "Jack"
NewCard.Value = 10
Case 11
NewCard.Name = "Queen"
NewCard.Value = 10
Case 12
NewCard.Name = "King"
NewCard.Value = 10
End Select
This doesn't end up organizing them the way they would be in a fresh, newly purchased deck of cards (organized first by suit and then by rank). Instead, the first card is the Ace of Hearts followed by the 2 of Diamonds, then the 3 of Clubs, and so on. The fourteenth card is the Ace of Diamonds, then the 2 of Clubs, and so on.
At any rate, suffice it to say that all the cards get created and assigned appropriate values, for as many decks as were requested.
Adding the card to the shoe
The last line in the loop adds this NewCard to the CardDeck data structure:
CardDeck.Push(NewCard)
Count, shuffle, and draw
The last three items in the Shoe class are pretty straightforward. They essentially just wrap the functionality in the Randomized Stack class.
The Count property provides read-only access to the number of cards in the deck:
Public ReadOnly Property Count As Integer
Get
Return CardDeck.Count
End Get
End Property
The Shuffle method just turns around and calls the RandomizeOrder method:
Public Sub Shuffle
CardDeck.RandomizeOrder
End Sub
The Draw method pops the top card off the deck and returns it to the calling routine. Because the objects in the CardDeck Randomized Stack are all of type object, I use the CType command to convert the object to a Card before returning it.
Public Function Draw As Card
Return(CType(CardDeck.Pop,Card))
End function
Saving and compiling the Shoe class
This file should be saved under the name playingcards.vb in the blackjack\bin folder. And just as you did with rndstk.vb, you must compile it into a DLL. The only difference is that the class in this file makes use of classes in the rndstk.dll, so you must include a reference to it in the line you type to compile. You do that with the /r compiler option.
vbc /t:library /r:rndstk.dll playingcards.vb
Summing up the shoe
You may feel that my approach to creating the Shoe object was overly complicated. And it's true that I could easily have collapsed together the functionality of the Randomized Stack and the Shoe into one class. The main reason I didn't was to demonstrate how decoupling functionality can, in turn, make the final classes you create more reusable.
And while, in this case, how and where to do that is fairly straightforward, it isn't always that clear. When business objects are intertwined in complex and intricate ways and future plans for the software are hazy at best, it's often difficult to decide how best to decompose and decouple your classes. And you won't get it right the
first time or even the fifteenth. But as you try to discover what works and what doesn't work, you'll gain the experience you need to make smaller mistakes in the future. What's the final result? Well, now you have a handy class that you can instantiate in any page. When you do, it will automatically be holding one or more decks of standard playing cards in the shoe. Use the Shuffle method to mix them up and the Draw method to start pulling cards off the top. Pretty handy!
Now it's time to put your cards to work in a quick game of blackjack.
The Blackjack Page
The entire blackjack game takes place on a single page. This is a tribute to the dynamic nature of ASP.NET. Web Forms provide a flexible, easy-to-use environment for creating dynamic user interfaces. It's not uncommon to radically reduce the number of pages on your site when moving from static pages to ASP.NET.
Here's the complete source code for the Blackjack page. Put this in a file named blackjack.aspx and save it in the blackjack folder (not in \bin).
<%@ Page Language="VB" Explicit="true" Strict="true" Debug="true" %>
<%@Import Namespace="PlayingCards" %>
<html>
<head/>
<script language="VB" runat=server>
Dim GameShoe As Shoe
Dim Player As Participant
Dim PlayerLabels As ArrayList
Dim Dealer As Participant
Dim DealerLabels As ArrayList
Class Participant
Public Hand As ArrayList
Sub New()
Hand = New ArrayList
End Sub
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
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
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)
If Not IsPostBack Then
StartGame
Else
GameShoe = CType(Session("GameShoe"),Shoe)
Player = CType(Session("Player"),Participant)
Dealer = CType(Session("Dealer"),Participant)
End If
End Sub
Sub Page_PreRender(sender As Object, e As EventArgs)
Session("GameShoe") = GameShoe
Session("Player") = Player
Session("Dealer") = Dealer
End Sub
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
Sub EndGame
Hit.Enabled = False
Stand.Enabled = False
PlayAgain.visible=True
Yes.visible=True
No.Visible=True
End Sub
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
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
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
Sub Stand_Click(Sender As Object, E As EventArgs) Message.Text = "You decided to stand on " & _
CStr(Player.TotalValue)
DealerPlay
End Sub
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
Sub No_Click(Sender As Object, E As EventArgs)
Yes.visible = False
No.visible = False
PlayAgain.text = "Thanks For Playing!"
End Sub
</script>
<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>
</html>
The preceding is a single page, but not a simple page! However, the page can easily be divided into its component parts, which act as building blocks for putting the whole thing together. The next several sections describe these building blocks and how they come together to create the complete game.
Getting started
The beginning of the listing has a few important things to note. First, there's an Import directive to include the PlayingCards namespace that contains both the Card structure and the Shoe class:
<%@Import Namespace="PlayingCards" %>
Then, just inside the <script> tag, some page-level variables are declared:
<script language="VB" runat=server>
Dim GameShoe As Shoe
Dim Player As Participant
Dim PlayerLabels As ArrayList
Dim Dealer As Participant
Dim DealerLabels As ArrayList
GameShoe is a variable that will hold an instance of the Shoe class and will be the collection of cards used to deal this game.
Player and Dealer are both instances of the Participant class that is created on this page. I'll discuss this class in the next section.
PlayerLabels and DealerLabels are ArrayLists that will contain all the label controls on this page used for displaying the names of the cards in the player and dealer hands. By putting the labels in an array list, they become easier to reference by index. This technique is one that is used to make up for the fact that Web Forms don't include the ability to create control arrays in the same way that you could in Visual Basic 6. You'll see how this works later when I describe the code that uses these ArrayLists.
The participant
As I began developing this page, I found myself doing the same work twice in lots of places. Both the player and the dealer share several common needs for data storage and functionality, so I decided to centralize this and thus created the Participant class. This class brings together the common functionality and data-storage needs into one place and makes the code easier to read and understand. Another big benefit of creating this class is that if, in the future, this page is extended to support multiple players, the process will be simplified, because you can just instantiate additional Participant objects.