22.5. Session Tracking in Web Services In Chapter 21, we described the advantages of maintaining information about users to personalize their experiences. In particular, we discussed session tracking using cookies and HttpSessionState objects. We will now incorporate session tracking into a Web service. Suppose a client application needs to call several methods from the same Web service, possibly several times each. In such a case, it can be beneficial for the Web service to maintain state information for the client. Session tracking eliminates the need for information about the client to be passed between the client and the Web service multiple times. For example, a Web service providing access to local restaurant reviews would benefit from storing the client user's street address. Once the user's address is stored in a session variable, Web methods can return personalized, localized results without requiring that the address be passed in each method call. This not only improves performance, but also requires less effort on the part of the programmerless information is passed in each method call. 22.5.1. Creating a Blackjack Web Service Storing session information can provide client programmers with a more intuitive Web service. Our next example is a Web service that assists programmers in developing a blackjack card game (Fig. 22.18). The Web service provides Web methods to deal a card and to evaluate a hand of cards. After presenting the Web service, we use it to serve as the dealer for a game of blackjack (Fig. 22.19). The blackjack Web service uses a session variable to maintain a unique deck of cards for each client application. Several clients can use the service at the same time, but Web method calls made by a specific client use only the deck stored in that client's session. Our example uses a simple subset of casino blackjack rules: Two cards each are dealt to the dealer and the player. The player's cards are dealt face up. Only the first of the dealer's cards is dealt face up. Each card has a value. A card numbered 2 through 10 is worth its face value. Jacks, queens and kings each count as 10. Aces can count as 1 or 11whichever value is more beneficial to the player (as we will soon see). If the sum of the player's two initial cards is 21 (i.e., the player was dealt a card valued at 10 and an ace, which counts as 11 in this situation), the player has "blackjack" and immediately wins the game. Otherwise, the player can begin taking additional cards one at a time. These cards are dealt face up, and the player decides when to stop taking cards. If the player "busts" (i.e., the sum of the player's cards exceeds 21), the game is over, and the player loses. When the player is satisfied with the current set of cards, the player "stays" (i.e., stops taking cards), and the dealer's hidden card is revealed. If the dealer's total is 16 or less, the dealer must take another card; otherwise, the dealer must stay. The dealer must continue to take cards until the sum of the dealer's cards is greater than or equal to 17. If the dealer exceeds 21, the player wins. Otherwise, the hand with the higher point total wins. If the dealer and the player have the same point total, the game is a "push" (i.e., a tie), and no one wins. Figure 22.18. Blackjack Web service. 1 ' Fig. 22.18: BlackjackWebService.vb 2 ' Blackjack Web Service deals and counts cards. 3 Imports System.Web 4 Imports System.Web.Services 5 Imports System.Web.Services.Protocols 6 7 <WebService(Namespace:="http://www.deitel.com/", Description:= _ 8 "A Web service that deals and counts cards for the game Blackjack")> _ 9 <WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _ 10 <Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _ 11 Public Class BlackjackWebService 12 Inherits System.Web.Services.WebService 13 14 ' deals card that has not yet been dealt 15 <WebMethod(EnableSession:=True, _ 16 Description:="Deal a new card from the deck.")> _ 17 Public Function DealCard() As String 18 ' get client's deck 19 Dim deck As ArrayList = CType(Session("deck"), ArrayList) 20 Dim card As String = Convert.ToString(deck(0)) 21 deck.RemoveAt(0) 22 Return card 23 End Function ' DealCard 24 25 ' creates and shuffles a deck of cards 26 <WebMethod(EnableSession:=True, _ 27 Description:="Create and shuffle a deck of cards.")> _ 28 Public Sub Shuffle() 29 Dim temporary As Object ' holds card temporarily during swapping 30 Dim randomObject As New Random() ' generates random numbers 31 Dim newIndex As Integer ' index of randomly selected card 32 Dim deck As New ArrayList() ' stores deck of cards (strings) 33 34 ' generate all possible cards 35 For face As Integer = 1 To 13 ' loop through face values 36 For suit As Integer = 0 To 3 ' loop through suits 37 deck.Add(face & " " & suit) ' add card (string) to deck 38 Next 39 Next 40 41 ' shuffles deck by swapping each card with another card randomly 42 For i As Integer = 0 To deck.Count - 1 43 ' get random index 44 newIndex = randomObject.Next(deck.Count - 1) 45 temporary = deck(i) ' save current card in temporary variable 46 deck(i) = deck(newIndex) ' copy randomly selected card 47 deck(newIndex) = temporary ' copy current card back into deck 48 Next 49 50 Session.Add("deck", deck) ' add this deck to user's session state 51 End Sub ' Shuffle 52 53 ' computes value of hand 54 <WebMethod(Description:= _ 55 "Compute a numerical value for the current hand.")> _ 56 Public Function GetHandValue(ByVal dealt As String) As Integer 57 ' split string containing all cards 58 Dim tab As Char() = New Char() {Convert.ToChar(vbTab)} 59 Dim cards As String() = dealt.Split(tab) ' get array of cards 60 Dim total As Integer = 0 ' total value of cards in hand 61 Dim face As integer ' face of the current card 62 Dim aceCount As Integer = 0 ' number of aces in hand 63 64 ' loop through the cards in the hand 65 For Each drawn As String In cards 66 ' get face of card 67 face = Int32.Parse(drawn.Substring(0, drawn.IndexOf(" "))) 68 69 Select Case face 70 Case 1 ' if ace, increment aceCount 71 aceCount += 1 72 Case 11 To 13 ' if jack, queen or king add 10 73 total += 10 74 Case Else ' otherwise, add value of face 75 total += face 76 End Select 77 Next 78 79 ' if there are any aces, calculate optimum total 80 If aceCount > 0 Then 81 ' if it is possible to count one ace as 11, and the rest 82 ' as 1 each, do so otherwise, count all aces as 1 each 83 If (total + 11 + aceCount - 1 <= 21) Then 84 total += 11 + aceCount - 1 85 Else 86 total += aceCount 87 End If 88 End If 89 90 Return total 91 End Function ' GetHandValue 92 End Class ' BlackjackWebService | Figure 22.19. Blackjack game that uses the Blackjack Web service. 1 ' Fig. 22.19: FrmBlackjack.vb 2 ' Blackjack game that uses the Blackjack Web service. 3 Imports System.Net 4 5 Public Class FrmBlackjack 6 ' reference to Web service 7 Private dealer As localhost.BlackjackWebService 8 9 ' string representing the dealer's cards 10 Private dealersCards As String 11 12 ' string representing the player's cards 13 Private playersCards As String 14 Private cardBoxes As ArrayList ' list of PictureBoxes for card images 15 Private currentPlayerCard As Integer ' player's current card number 16 Private currentDealerCard As Integer ' dealer's current card number 17 18 ' enum representing the possible game outcomes 19 Public Enum GameStatus 20 PUSH ' game ends in a tie 21 LOSE ' player loses 22 WIN ' player wins 23 BLACKJACK ' player has blackjack 24 End Enum ' GameStatus 25 26 ' sets up the game 27 Private Sub FrmBlackjack_Load(ByVal sender As Object, _ 28 ByVal e As System.EventArgs) Handles Me.Load 29 ' instantiate object allowing communication with Web service 30 dealer = New localhost.BlackjackWebService() 31 32 ' allow session state 33 dealer.CookieContainer = New CookieContainer() 34 cardBoxes = New ArrayList() 35 36 ' put PictureBoxes into cardBoxes ArrayList 37 cardBoxes.Add(pictureBox1) 38 cardBoxes.Add(pictureBox2) 39 cardBoxes.Add(pictureBox3) 40 cardBoxes.Add(pictureBox4) 41 cardBoxes.Add(pictureBox5) 42 cardBoxes.Add(pictureBox6) 43 cardBoxes.Add(pictureBox7) 44 cardBoxes.Add(pictureBox8) 45 cardBoxes.Add(pictureBox9) 46 cardBoxes.Add(pictureBox10) 47 cardBoxes.Add(pictureBox11) 48 cardBoxes.Add(pictureBox12) 49 cardBoxes.Add(pictureBox13) 50 cardBoxes.Add(pictureBox14) 51 cardBoxes.Add(pictureBox15) 52 cardBoxes.Add(pictureBox16) 53 cardBoxes.Add(pictureBox17) 54 cardBoxes.Add(pictureBox18) 55 cardBoxes.Add(pictureBox19) 56 cardBoxes.Add(pictureBox20) 57 cardBoxes.Add(pictureBox21) 58 cardBoxes.Add(pictureBox22) 59 End Sub ' FrmBlackjack_Load 60 61 ' deals cards to dealer while dealer's total is less than 17, 62 ' then computes value of each hand and determines winner 63 Private Sub DealerPlay() 64 ' reveal dealer's second card 65 Dim tab As Char() = New Char() {Convert.ToChar(vbTab)} 66 Dim cards As String() = dealersCards.Split(tab) 67 DisplayCard(1, cards(1)) 68 69 Dim nextCard As String 70 71 ' while value of dealer's hand is below 17, 72 ' dealer must take cards 73 While dealer.GetHandValue(dealersCards) < 17 74 nextCard = dealer.DealCard() ' deal new card 75 dealersCards &= vbTab & nextCard 76 77 ' update GUI to show new card 78 MessageBox.Show("Dealer takes a card") 79 DisplayCard(currentDealerCard, nextCard) 80 currentDealerCard += 1 81 End While 82 83 Dim dealerTotal As Integer = dealer.GetHandValue(dealersCards) 84 Dim playerTotal As Integer = dealer.GetHandValue(playersCards) 85 86 ' if dealer busted, player wins 87 If dealerTotal > 21 Then 88 GameOver(GameStatus.WIN) 89 Else 90 ' if dealer and player have not exceeded 21, 91 ' higher score wins equal scores is a push. 92 If dealerTotal > playerTotal Then 93 GameOver(GameStatus.LOSE) 94 ElseIf playerTotal > dealerTotal Then 95 GameOver(GameStatus.WIN) 96 Else 97 GameOver(GameStatus.PUSH) 98 End If 99 End If 100 End Sub ' DealerPlay 101 102 ' displays card represented by cardValue in specified PictureBox 103 Public Sub DisplayCard( _ 104 ByVal card As Integer, ByVal cardValue As String) 105 ' retrieve appropriate PictureBox from ArrayList 106 Dim displayBox As PictureBox = CType(cardBoxes(card), PictureBox) 107 108 ' if string representing card is empty, 109 ' set displayBox to display back of card 110 If cardValue = "" Then 111 displayBox.Image = _ 112 Image.FromFile("blackjack_images/cardback.png") 113 Return 114 End If 115 116 ' retrieve face value of card from cardValue 117 Dim face As String = _ 118 cardValue.Substring(0, cardValue.IndexOf(" ")) 119 120 ' retrieve the suit of the card from cardValue 121 Dim suit As String = _ 122 cardValue.Substring(cardValue.IndexOf(" ") + 1) 123 124 Dim suitLetter As Char ' suit letter used to form image file name 125 126 ' determine the suit letter of the card 127 Select Case Convert.ToInt32(suit) 128 Case 0 ' clubs 129 suitLetter = "c"c 130 Case 1 ' diamonds 131 suitLetter = "d"c 132 Case 2 ' hearts 133 suitLetter = "h"c 134 Case Else ' spades 135 suitLetter = "s"c 136 End Select 137 138 ' set displayBox to display appropriate image 139 displayBox.Image = Image.FromFile( _ 140 "blackjack_images/" & face & suitLetter & ".png") 141 End Sub ' DisplayCard 142 143 ' displays all player cards and shows 144 ' appropriate game status message 145 Public Sub GameOver(ByVal winner As GameStatus) 146 ' display appropriate status image 147 If winner = GameStatus.PUSH Then ' push 148 statusPictureBox.Image = _ 149 Image.FromFile("blackjack_images/tie.png") 150 ElseIf winner = GameStatus.LOSE Then ' player loses 151 statusPictureBox.Image = _ 152 Image.FromFile("blackjack_images/lose.png") 153 ElseIf winner = GameStatus.BLACKJACK Then 154 ' player has blackjack 155 statusPictureBox.Image = _ 156 Image.FromFile("blackjack_images/blackjack.png") 157 Else ' player wins 158 statusPictureBox.Image = _ 159 Image.FromFile("blackjack_images/win.png") 160 End If 161 162 ' display final totals for dealer and player 163 lblDealerTotal.Text = _ 164 "Dealer: " & dealer.GetHandValue(dealersCards) 165 lblPlayerTotal.Text = _ 166 "Player: " & dealer.GetHandValue(playersCards) 167 168 ' reset controls for new game 169 btnStay.Enabled = False 170 btnHit.Enabled = False 171 btnDeal.Enabled = True 172 End Sub ' GameOver 173 174 ' deal two cards each to dealer and player 175 Private Sub btnDeal_Click(ByVal sender As System.Object, _ 176 ByVal e As System.EventArgs) Handles btnDeal.Click 177 Dim card As String ' stores a card temporarily until added to a hand 178 179 ' clear card images 180 For Each cardImage As PictureBox In cardBoxes 181 cardImage.Image = Nothing 182 Next 183 184 statusPictureBox.Image = Nothing ' clear status image 185 lblDealerTotal.Text = "" ' clear final total for dealer 186 lblPlayerTotal.Text = "" ' clear final total for player 187 188 ' create a new, shuffled deck on the remote machine 189 dealer.Shuffle() 190 191 ' deal two cards to player 192 playersCards = dealer.DealCard() ' deal a card to player's hand 193 194 ' update GUI to display new card 195 DisplayCard(11, playersCards) 196 card = dealer.DealCard() ' deal a second card 197 DisplayCard(12, card) ' update GUI to display new card 198 playersCards &= vbTab & card ' add second card to player's hand 199 200 ' deal two cards to dealer, only display face of first card 201 dealersCards = dealer.DealCard() ' deal a card to dealer's hand 202 DisplayCard(0, dealersCards) ' update GUI to display new card 203 card = dealer.DealCard() ' deal a second card 204 DisplayCard(1, "") ' update GUI to show face-down card 205 dealersCards &= vbTab & card ' add second card to dealer's hand 206 207 btnStay.Enabled = True ' allow player to stay 208 btnHit.Enabled = True ' allow player to hit 209 btnDeal.Enabled = False ' disable Deal Button 210 211 ' determine the value of the two hands 212 Dim dealerTotal As Integer = dealer.GetHandValue(dealersCards) 213 Dim playerTotal As Integer = dealer.GetHandValue(playersCards) 214 215 ' if hands equal 21, it is a push 216 If dealerTotal = playerTotal And dealerTotal = 21 Then 217 GameOver(GameStatus.PUSH) 218 ElseIf dealerTotal = 21 Then ' if dealer has 21, dealer wins 219 GameOver(GameStatus.LOSE) 220 ElseIf playerTotal = 21 Then ' player has blackjack 221 GameOver(GameStatus.BLACKJACK) 222 End If 223 224 currentDealerCard = 2 ' next dealer card has index 2 in cardBoxes 225 currentPlayerCard = 13 ' next player card has index 13 in cardBoxes 226 End Sub ' btnDeal_Click 227 228 ' deal another card to player 229 Private Sub btnHit_Click(ByVal sender As System.Object, _ 230 ByVal e As System.EventArgs) Handles btnHit.Click 231 ' get player another card 232 Dim card As String = dealer.DealCard() ' deal new card 233 playersCards &= vbTab & card ' add new card to player's hand 234 235 ' update GUI to show new card 236 DisplayCard(currentPlayerCard, card) 237 currentPlayerCard += 1 238 239 ' determine the value of the player's hand 240 Dim total As Integer = dealer.GetHandValue(playersCards) 241 242 ' if player exceeds 21, house wins 243 If total > 21 Then 244 GameOver(GameStatus.LOSE) 245 End If 246 247 ' if player has 21, 248 ' they cannot take more cards, and dealer plays 249 If total = 21 Then 250 btnHit.Enabled = False 251 DealerPlay() 252 End If 253 End Sub ' btnHit_Click 254 255 ' play the dealer's hand after the play chooses to stay 256 Private Sub btnStay_Click(ByVal sender As System.Object, _ 257 ByVal e As System.EventArgs) Handles btnStay.Click 258 btnStay.Enabled = False ' disable Stay Button 259 btnHit.Enabled = False ' display Hit Button 260 btnDeal.Enabled = True ' re-enable Deal Button 261 DealerPlay() ' player chose to stay, so play the dealer's hand 262 End Sub ' btnStay_Click 263 End Class ' FrmBlackjack
a) Initial cards dealt to the player and the dealer when the user pressed the Deal button.
b) Cards after the player pressed the Hit button twice, then the Stay button. In this case, the player won the game with a higher total than the dealer.
c) Cards after the player pressed the Hit button once, then the Stay button. In this case, the player busted (exceeded 21) and the dealer won the game.
d) Cards after the player pressed the Deal button. In this case, the player won with Blackjack because the first two cards were an ace and a card with a value of 10 (a jack in this case).
e) Cards after the player pressed the Stay button. In this case, the player and dealer pushthey have the same card total. | The Web service (Fig. 22.18) provides methods to deal a card and to determine the point value of a hand. We represent each card as a String consisting of a digit (e.g., 113) representing the card's face (e.g., ace through king), followed by a space and a digit (e.g., 03) representing the card's suit (e.g., clubs, diamonds, hearts or spades). For example, the jack of hearts is represented as "11 2", and the two of clubs is represented as "2 0". After deploying the Web service, we create a Windows application that uses the BlackjackWebService's Web methods to implement a game of blackjack. To create and deploy this Web service follow the steps presented in Sections 22.4.222.4.3 for the HugeInteger service. Lines 1516 define method DealCard as a Web method. Setting property EnableSession to true indicates that session information should be maintained and should be accessible to this method. This is required only for methods that must access the session information. Doing so allows the Web service to use an HttpSessionState object (named Session by ASP.NET) to maintain the deck of cards for each client application that uses this Web service (line 22). We can use Session to store objects for a specific client between method calls. We discussed session state in detail in Chapter 21. Method DealCard removes a card from the deck and sends it to the client. Without using a session variable, the deck of cards would need to be passed back and forth with each method call. Using session state makes the method easy to call (it requires no arguments), and avoids the overhead of sending the deck over the network multiple times. At this point, our Web service contains methods that use session variables. However, the Web service still cannot determine which session variables belong to which user. If two clients successfully call the DealCard method, the same deck would be manipulated. To avoid this problem, the Web service automatically creates a cookie to uniquely identify each client. A Web browser client that has cookie handling enabled stores cookies automatically. A non-browser client application that consumes this Web service must create a CookieContainer object to store cookies sent from the server. We discuss this in more detail in Section 22.5.2, when we examine the blackjack Web service's client. Web method DealCard (lines 1523) selects a card from the deck and sends it to the client. The method first obtains the current user's deck as an ArrayList from the Web service's Session object (line 19). After obtaining the user's deck, DealCard removes the top card from the deck (line 21) and returns the card's value as a String (line 22). Method Shuffle (lines 2651) generates an ArrayList representing a deck of cards, shuffles it and stores it cards in the client's Session object. Lines 3539 generate Strings in the form "face suit" to represent each card in a deck. Lines 4248 shuffle the deck by swapping each card with another randomly selected card. Line 50 adds the ArrayList to the Session object to maintain the deck between method calls from a particular client. Method GetHandValue (lines 5491) determines the total value of the cards in a hand by trying to attain the highest score possible without going over 21. Recall that an ace can be counted as either 1 or 11, and all face cards count as 10. As you will see in Fig. 22.19, the client application maintains a hand of cards as a String in which each card is separated by a tab character. Line 59 tokenizes the hand of cards (represented by dealt) into individual cards by calling String method Split and passing to it an array that contains the delimiter characters (in this case, just a tab). Split uses the delimiter characters to separate tokens in the String. Lines 6577 count the value of each card. Line 67 retrieves the first integerthe faceand uses that value in the Select Case statement (lines 6976). If the card is an ace, the method increments variable aceCount (line 71). We discuss how this variable is used shortly. If the card is an 11, 12 or 13 (jack, queen or king), the method adds 10 to the total value of the hand (line 73). If the card is anything else, the method increases the total by that value (line 75). Because an ace can have either of two values, additional logic is required to process aces. Lines 8088 process the aces after all the other cards. If a hand contains several aces, only one ace can be counted as 11 (if two aces each are counted as 11, the hand would have a losing value of 22). The condition in line 83 determines whether counting one ace as 11 and the rest as 1 will result in a total that does not exceed 21. If this is possible, line 84 adjusts the total accordingly. Otherwise, line 86 adjusts the total, counting each ace as 1. Method GetHandValue maximizes the value of the current cards without exceeding 21. Imagine, for example, that the dealer has a 7 and receives an ace. The new total could be either 8 or 18. However, GetHandValue always maximizes the value of the cards without going over 21, so the new total is 18. 22.5.2. Consuming the Blackjack Web Service Now we use the blackjack Web service in a Windows application (Fig. 22.19). This application uses an instance of BlackjackWeService (declared in line 7 and created in line 30) to represent the dealer. The Web service keeps track of the player's and the dealer's cards (i.e., all the cards that have been dealt). As in Section 22.4.4, you must add a Web reference to your project so it can access the Web service. The code and images for this example are provided with the chapter's examples, which can be downloaded from our Web site: www.deitel.com/books/vbforprogrammers2. Each player has 11 PictureBoxesthe maximum number of cards that can be dealt without automatically exceeding 21 (i.e., four aces, four twos and three threes). These PictureBoxes are placed in an ArrayList (lines 3758), so we can index the ArrayList during the game to determine the PictureBox that will display a particular card image. In Section 22.5.1, we mentioned that the client must provide a way to accept cookies created by the Web service to uniquely identify users. Line 33 in BlackjackForm's Load event handler creates a new CookieContainer object for the dealer's CookieContainer property. A CookieContainer (namespace System.Net) stores the information from a cookie (created by the Web service) in a Cookie object in the CookieContainer. The Cookie contains a unique identifier that the Web service can use to recognize the client when the client makes future requests. As part of each request, the cookie is automatically sent back to the server. If the client did not create a CookieContainer object, the Web service would create a new Session object for each request, and the user's state information would not persist across requests. Method GameOver (lines 145172) shows an appropriate message in the status PictureBox and displays the final point totals of both the dealer and the player. These values are obtained by calling the Web service's GetHandValue method in lines 164 and 166. Method GameOver receives as an argument a member of the GameStatus enumeration (defined in lines 1924). The enumeration represents whether the player tied, lost or won the game; its four members are PUSH, LOSE, WIN and BLACKJACK. When the player clicks the Deal button, the event handler (lines 175226) clears all of the PictureBoxes and the Labels displaying the final point totals. Line 189 shuffles the deck by calling the Web Service's Shuffle method, then the player and dealer receive two cards each (returned by calls to the Web service's DealCard method in lines 192, 196, 201 and 203). Lines 212213 evaluate both the dealer's and player's hands by calling the Web service's GetHandValue method. If the player and the dealer both obtain scores of 21, the program calls method GameOver, passing GameStatus.PUSH. If only the player has 21 after the first two cards are dealt, the program passes GameStatus.BLACKJACK to method GameOver. If only the dealer has 21, the program passes GameStatus.LOSE to method GameOver. If dealButton_Click does not call GameOver, the player can take more cards by clicking the Hit button. The event handler for this button is in lines 229253. Each time a player clicks Hit, the program deals the player one more card (line 232) and displays it in the GUI. Line 240 evaluates the player's hand. If the player exceeds 21, the game is over, and the player loses. If the player has exactly 21, the player is not allowed to take any more cards, and method DealerPlay (lines 63100) is called, causing the dealer to keep taking cards until the dealer's hand has a value of 17 or more (lines 7381). If the dealer exceeds 21, the player wins (line 88); otherwise, the values of the hands are compared, and GameOver is called with the appropriate argument (lines 9298). Clicking the Stay button indicates that a player does not want to be dealt another card. The event handler for this button (lines 256262) disables the Hit and Stay buttons, then calls method DealerPlay. Method DisplayCard (lines 103141) updates the GUI to display a newly dealt card. The method takes as arguments an integer representing the index of the PictureBox in the ArrayList that must have its image set, and a String representing the card. An empty String indicates that we wish to display the card face down. If method DisplayCard receives a String that's not empty, the program extracts the face and suit from the String and uses this information to find the correct image. The Select Case statement (lines 127136) converts the number representing the suit to an integer and assigns the appropriate character to suitLetter (c for clubs, d for diamonds, h for hearts and s for spades). The character in suitLetter is used to complete the image's file name (lines 139140). |