How do I shuffle and deal cards one at a time to players?
This is the wrong way to go about it:
Dim rand As New Random
Dim rand1 As Integer = rand.Next(12)
Dim rand2 As Integer = rand.Next(3)
You can easily end up with duplicate cards because you are picking THIS card without knowing if it has already been dealt (even in this click!). You also want one Random used per game/app, not per card. Representing a Card as suit & face
will work, but it glues 2 important pieces of information together - in most games you will later have to parse it to get that info.
A deck is made up of 52 cards; each Card is made of a Suit and a Rank. Lets build a simple class or two which mimics that:
Public Class Card
Public Property Suit As String
Public Property Rank As Integer
' card images from
' http://www.jfitz.com/cards/
Public Property Img As Image
Private Faces() As String = {"Jack", "Queen", "King", "Ace"}
' for text version of the game
Public Function CardText() As String
Dim tmp As String = Rank.ToString
If Rank = 1 Then
tmp = "Ace"
ElseIf Rank >= 11 Then
tmp = Faces(Rank - 11)
End If
Return String.Format("{0} of {1}", tmp, Suit)
End Function
' iDeck class will assign Rank, Suit and img to an "empty" card
Public Sub New(strSuit As String, nRank As Integer, i As Image)
Suit = strSuit
Rank = nRank
Img = i
End Sub
Public Overrides Function ToString() As String
Return CardText()
End Function
End Class
In reality, you'd also want a Value property since in most games it is not the same as the Rank.
With the Rank and Suit as individual properties you can test the cards of one player versus another to see who has the best card. This is easy in some games like BlackJack since all you will care about is the Rank and the sum. Hand evaluation is more complex in other games since you can have combos like FullHouse and Flush. Now the deck (with both shuffle methods there for illustrative purposes):
Public Class Deck
Dim rand As Random
' the deck will be built in the same order a real deck comes in
Private Suits() As String = {"Spades", "Diamonds", "Clubs", "Hearts"}
Private Rank() As Integer = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}
' useful for blackjack
Private Const Ace As Integer = 1
' freshly opened pack where they are in order. this is reused rather
' than building a new deck each time
Private freshDeck As List(Of Card)
' shuffled deck; Stack prevents any bugs from a botched counter var
Private shoe As Stack(Of Card)
' using an imagelist but My.Resources could work depending on card names
Private imglist As ImageList
' the GAME object passes us the imagelist holding the card pics
Public Sub New(imgs As ImageList) ' ctor
' new random ONCE
rand = New Random
imglist = imgs
NewDeck()
End Sub
' create a new deck (done ONCE) but could be called again
Private Sub NewDeck()
freshDeck = New List(Of Card) ' new object
For Each s As String In Suits
For Each n As Integer In Rank
Dim key As String = CardKey(s, n)
freshDeck.Add(New Card(s, n, imglist.Images(key)))
Next
Next
End Sub
Private keys() As String = {"J", "Q", "K"}
Private Function CardKey(suit As String, rank As Integer) As String
' convert Suit / Key to the key used in the imglist
' (e.g C1.JPG for Clubs, Ace)
' cards come from http://www.jfitz.com/cards/
' use the windows set (or rename them all)
Dim key As String = suit.Substring(0, 1) ' => C, H, D, S
If rank < 11 Then
key &= rank.ToString
Else
key &= keys(rank - 11) ' cvt 11, 12, 13 => J, Q, K
End If
Return key & ".png"
End Function
' Shuffle deck using Fisher-Yates; sub optimal here since we "use up"
' the shoe each hand and are not reshuffling a deck
Public Sub Shuffle()
' new temp deck preserves the new deck starting point
Dim thisDeck As New List(Of Card)(freshDeck.ToArray)
Dim tmp As Card
Dim j As Integer
' hi to low, so the rand pick result is meaningful
' lo to hi introduces a definite bias
For i As Integer = thisDeck.Count - 1 To 0 Step -1
j = rand.Next(0, i + 1) ' NB max param is EXCLUSIVE
tmp = thisDeck(j)
' swap Card j and Card i
thisDeck(j) = thisDeck(i)
thisDeck(i) = tmp
Next
' using a stack for the actual deck in use; copy shuffled deck to the Shoe
shoe = New Stack(Of Card)(thisDeck.ToArray)
End Sub
' shuffle using random and LINQ (neo's answer)
Public Sub ShuffleLinq()
' using the same rand per app run may be random enough
' but would not suffice for most 'serious' games or standards
shoe = New Stack(Of Card)(freshDeck.OrderBy(Function() rand.Next))
End Sub
Public Function DealCard() As Card
' get a card
If shoe.Count = 0 Then
' ToDo: out of cards
' happens with 9+ handed, 7 card games and many hi-lo games...
' usually mucked and burn cards are reshuffled
' some games use shared cards at the end
' (muck/burn list not implemented)
End If
Return shoe.Pop
End Function
End Class
Rather than simply looking for code to paste, you should start trying to learn concepts presented (even if it is just the learning the names of concepts you want/need to learn more about: Classes, Enum, Collections, Objects, Methods...). The above is much more involved than a simple array, but if you study it you'll see Card
and Deck
mimic the real world versions. The deck builds itself so elsewhere we just have to use it.
Next is a Player Class to hold the cards and a Game Class to implement the rules for the game, deal cards and control the order (these are left for the student to complete). This results in almost no code in the form, just some calls to Game (and only Game) which in turn uses Deck and Player, controls turns, give cards to players etc. e.g.:
Private poker As Game
...
New Game(theImgList, 3) ' 3 == the human player
Shuffle button:
poker.ShuffleDeck()
poker.NewHand()
thisRound = Game.Rounds.HoleCards
Deal button:
Select Case thisRound
Case Game.Rounds.HoleCards
poker.NewHand() ' clears the display etc
poker.DealRound(thisRound) ' deal cards
thisRound = Game.Rounds.Flop ' change round indicator
Case Game.Rounds.Flop ' even this could be internal to Game(poker)
poker.DealRound(thisRound)
thisRound = Game.Rounds.Turn
In Game.DealRound:
Case Rounds.Flop
myDeck.DealCard() ' burn card
players(0).AddCard(myDeck.DealCard) ' Player(0) is the house or community
players(0).AddCard(myDeck.DealCard)
players(0).AddCard(myDeck.DealCard)
Using classes, the form doesnt know or care HOW anything happens (like which suffle method), just that it happens when requested. Texas HoldEm Game, where the Community Cards are held by Player(0) who has an IsHouse
property and some other player who IsHuman
(basically so their cards always show):
.o0( Yea, "Jon" go all in, PLEASE go all in.)
And I definitely want to see a '6' come out for "Neo". Definitely a '6' for "Neo".
You need to generate the whole deck upfront (52 cards), store it in a List/Stack/Queue, and when required, deal one to the player.
A double loop should be good enough to generate cards sequentially, then sort by random number:
Dim Suits() As String = {"S", "D", "C", "H"}
Dim Faces() As String = {"2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K", "A"}
Dim cards As New List(Of String)
For Each s As String In Suits
For Each f As String In Faces
cards.Add(s & f)
Next
Next
Dim r As New Random
Dim cardsShuffled = cards.OrderBy(Function() r.Next)
EDIT: Here is how you can populate your labels (just one way of doing it):
Dim deck As New Stack(Of String)(cardsShuffled)
For Each lbl As Label in {Label1, Label2, Label3, ...} 'you need to write all
Try
lbl.Text = deck.Pop()
Catch ex As InvalidOperationException
MessageBox.Show("No more cards.")
End Try
Next
Reference:
- Stack(Of T).Pop @ MSDN.
A proper solution would be to create labels dynamically, but first make sure you can get this to work. Refactoring is usually done after you have a working product.