There is not a shuffle method in the deck class - The getCard() method actually returns a random card from the deck. Though this works... it's not actually how cards are played. We decided to modify the logic a bit in our Forth implementation and actually have the deck of cards shuffled and have the "get a card" functionality just grab the "top" card on the deck.
To start with we represent a deck of cards in memory by using the create word, deck, as follows:
create deck 52 allot
This will allocate 52 slots in memory starting at the address of the deck word. In order to work with "cards" in the deck we created a word, deckcard, which represents an address location (one of the 52 spots in the allocated memory) of a "card". The definition of deckcard is as follows:
: deckcard deck + ;
So, to get an actual card representation (0-51) we can first push the number we are looking for, followed by the deckcard word. So, for example, to get the 9th card in the deck we could use the following code:
9 deckcard ( address of the 9th card )
c@ ( actual card, represented as 0-51 )
Building a Deck
So, once we built a way of representing a deck of cards in memory and a way of accessing slots within that "deck" to represent cards we needed to build a word for, well, building a deck by populating it with "cards". This word basically loops through 52 times and uses the deckcard word to store card representations (0-51) in the deck.
: builddeck 52 0 do ( loop 52 to 0 )
i i ( push current index of loop to stack twice )
deckcard ( i i addr -- i addr+i ) ( get card address in deck )
c! ( i addr+i -- ) ( stores index of loop in deck )
loop
52 #cardsindeck ! ( set #cardsindeck variable )
;
Notice the #cardsindeck word - this is a variable we used for tracking the number of cards in the deck as they are removed from the deck. This word maps to the Java method getSize(). Its value is actually decremented in another word (discussed later in this post) that handles getting the top card off the deck (the next card). This variable basically represents a sliding counter which can be added to the starting address of the deck. So, at any given time, adding the value of this variable to the starting address of the deck will give you access to the next top card on the deck.
Shuffling a Deck
The builddeck word simply loads the 52 memory slots with 0-51, representing 52 cards, in order. In a effort to make the game play a bit more interesting and realistic we created a word for shuffling the deck, shuffledeck. This also involved building a helper word, swapcards, which swaps the value of the top two memory addresses on the stack.
: swapcards ( c1 c2 -- ) ( swap the cards at 2 mem addrs in deck )The random word used in the shuffledeck word is provided by Win32Forth which pushes a random number between 0 and number atop the stack.
2dup ( addr1 addr2 -- addr1 addr2 addr1 addr2 )
c@ ( addr1 addr2 addr1 addr2 -- addr1 addr2 addr1 n )
swap ( addr1 addr2 addr1 n2 -- addr1 addr2 n2 addr1 )
c@ ( addr1 addr2 n2 addr1 -- addr1 addr2 n2 n1 )
rot ( addr1 addr2 n2 n1 -- addr1 n2 n1 addr2 )
c! ( addr1 n2 n1 addr2 -- addr1 n2 )
swap ( addr1 n2 -- n2 addr1 )
c! ( n2 addr1 -- )
;
: shuffledeck 52 0 do ( loop 52 to 0 )
i ( push current index of loop to stack )
deckcard ( i addr -- addr+i ) ( get card address in deck )
52 random ( push random number 0-52 to stack )
deckcard ( addr+i r -- addr+i addr+r ) ( get random card addr )
swapcards ( addr+i addr+r -- addr+r addr+i )
loop
;
Our implementation of shuffling cards is a modified version of the one found in Thinking Forth by Leo Brodie.
Getting the Next Card
As mentioned earlier we created a word that gives us the number, or index, within the deck's allocated memory slots that represents the "next card on the deck".
: nextcardfromdeck
#cardsindeck @ ( current number of cards )
1 - ( offset for zero-based deck )
deckcard ( get card address in deck )
c@ ( get card value )
#cardsindeck @ 1 - ( subtract 1 from number of cards )
#cardsindeck ! ( update number of cards )
;
This word uses the #cardsindeck variable as described above to get the next card on the deck. It simply pushes the card representation (0-51) to the stack and then subtracts 1 from the tracking variable. At the moment, the slot within the deck isn't actually cleared out or freed up, but the #cardsindeck does a good job of keeping track of what slots are left or still available in the deck.
Representing Cards
Currently, within our deck implementation, we are only referring to cards as numbers between 0-51. We will figure out a way to provide meaning to these numbers such as suit and value when we start building our card logic.
Comparing this to our haskell card implementation, there's considerable complexity in doing creating the suit and rank of a card.end up almost having to create helper functions to go back to the 52 card count. In our case we used mod to create the ordering.
ReplyDeleteVery true, Evan.
ReplyDeleteChris and I are going in a similar direction. High-level - we'll take a given number (one of the 52) and based on that number's location within the set of 52 we'll know what it is. If it's in the second set of 13 (14-26) then it's a heart, for example. And then based on its count within that subset we'll determine which rank it is.
We'll have have a post up soon with more specific details on our implementation and how the Forth words we develop to support this will fall into place during game play.
Thanks for the comments, Evan!