Monday, April 25, 2011

Play a Card

The previous post discussed the logic used to determine if a card can legally be played according to rules of the game. Once a card is determined to be playable using that word, we need to actually discard the card on the the discard pile.

The discard pile is simply another allocation in memory similar to the deck and the hands.

The technical process for removing a card from a hand and placing it in the discard pile is as follows:

1) Put the card value in the next available memory slot in the discard pile allocation.
2) Remove the card value from the hand's memory allocation slot

Step 2 in this process took a bit of thinking - since we read a hand by traversing the memory slots until we reach a value of 0 we couldn't just replace the discarded card value with a 0. Suppose you were discarding the second card in a hand of 6 cards... the next time we iterate through that hand only the first card would be counted. The way we overcame this was copying the card value from the last slot in the hand's allocation to the slot of the discarded card value and then replacing the last slot's card value with 0.

For example:

Hand: 2 | 3 | 4 | 5 | 6

Discarding the 3 would result in the following hand:
Hand: 2 | 6 | 4 | 5


Here is the code. The second half handles moving the last card value into the discarded card's slot. There is also a check to see if the discarded card is the last card in the hand in which case we just replace it with 0.

: playcard
discardpile ( add card to discard pile )
gethandcardcount
swap
+
c!

dup
rot
gethandcardcount 1 -
rot
= if
+
0
swap
c!
else
dup
gethandcardcount 1 -
swap
+
c@ ( last card in hand )
rot rot
dup
gethandcardcount 1 -
swap
+
0
swap
c!
+
c!
then
;

Game Play Logic - ?playcard

In order to determine if a card is a "legal" move according to the rules of the Crazy 8s game, we developed this word, ?playcard. This word will place either true (-1) or false (0) on the stack.
: ?playcard
dup
13 mod 8 = if
drop ( drop dup )
true
else
discardpile
lastcard
dup
8 = if
getcardsuit
swap
getcardsuit
= if
true
else
false
then
else
2dup
13 mod
swap
13 mod
= if ( same rank/face )
drop drop ( drop 2dup )
true
else
getcardsuit
swap
getcardsuit
= if ( same suit )
true
else
false
then
then
then
then
;

Update: Displaying a Card

We have modified our code from the previous post for displaying cards.

Mainly, we broke out the suit determination logic into its own word for code reuse.

Here is the new version of our Card logic:

: getcardsuit
dup 14 < if 1 swap drop else ( 1=Diamonds )
dup 27 < if 2 swap drop else ( 2=Hearts )
dup 40 < if 3 swap drop else ( 3=Clubs )
dup 53 < if 4 swap drop else ( 4=Spades )
." Error - can't determine suit "
drop
then then then then
;

: getcardface
dup 1 = if ." Ace " else
dup 11 = if ." Jack " else
dup 12 = if ." Queen " else
dup 0 = if ." King " else
dup .
then then then then drop
;

: displaycard
dup ( c -- c1 c2 ) ( duplicate the card number )
13
mod ( c 13 -- c1 ) ( use mod division to get the card face # )

getcardface
getcardsuit

dup 1 = if ." of Diamonds " else
dup 2 = if ." of Hearts " else
dup 3 = if ." of Clubs " else
dup 4 = if ." of Spades " else
." Error Displaying Card "
then then then then drop
;

Friday, April 22, 2011

Displaying a Card

In previous posts we've made mention of cards as numbers between 0-51 (soon to be 1-52). In order to translate this card number to the user in the form of a playing card we wrote a word, displaycard, which take a card number from the stack and prints that card's Face/Suit to the prompt.

This is slightly different than our Pseudo code in that it uses the mod word to determine the card's rank within the suit.

: displaycard
dup ( c -- c1 c2 ) ( duplicate the card number )
13
mod ( c 13 -- c1 ) ( use mod division to get the card face # )

dup 1 = if ." Ace " else
dup 11 = if ." Jack " else
dup 12 = if ." Queen " else
dup 0 = if ." King " else
dup .
then then then then drop

dup 13 <= if ." of Diamonds " else
dup 26 <= if ." of Hearts " else
dup 39 <= if ." of Clubs " else
dup 52 <= if ." of Spades " else
." Error Displaying Card "
then then then then drop
;


Example of usage:
Assuming the number 34....

displaycard

8 of Clubs ok

In Action...

Determining Card Face and Rank - Pseudo code

This is our Pseudo code for how we will determine a card's rank and suit based on the cards index representation. For example, card #34 is 8 of Clubs.

Var cardNum

If cardNum < 13
If cardNum = 1
cardName = "ace"
Elseif cardNum = 11
cardName = "jack"
Elseif cardNum = 12
cardName = "queen"
Elseif cardNum = 13
cardName = "king"
Else
cardName = card.tostring()

cardName of Diamonds
Else
Suit = cardNum / 13
Suite1stcard = Suit * 13
Card = cardNum - suite1stcard

If card = 1
cardName = "ace"
Elseif card = 11
cardName = "jack"
Elseif card = 12
cardName = "queen"
Elseif card = 13
cardName = "king"
Else
cardName = card.tostring()

If suit = 1
cardName of hearts
Else if suit = 2
cardName of clubs
Else if suit = 3
cardName of spades
End

Thursday, April 14, 2011

Showing a Hand

We have created a word to display the cards in a specific hand:
: ?emptyhand
gethandcardcount 0=
;

: showhand
?emptyhand if
." Empty Hand "
else
gethandcardcount 0 do
dup
i
swap
+
c@ . ." "
loop
then
;

The use for this word is similiar to that of the drawcard word... simply push the hand in question to the stack and the the showhand word, as follows:
playerhand showhand

Below is a screen capture of drawcard and showhand in action.

playerhand drawcard ok

In our previous post we explained the Forth words we created for building and shuffling a deck as well as for getting the next card off the deck.

Now we have started building up a library of words for working with "hands" (a player's set of cards).

First of all, as in the original Java version, Crazy Eights is a two player game between the player and the dealer (computer). In the Java version these two hands are represented by instantiating instances of the Hand class.

In our Forth implementation we are representing the two hands by allocating space in memory as follows:
create playerhand 12 allot
create dealerhand 12 allot

This code creates two sets of 12 empty slots in memory.

Next we created a word for drawing a card:
: drawcard
nextcardfromdeck
swap
gethandcardcount
swap
+
c!
;

This word calls on two other words: nextcardfromdeck which gets the next card from the top of the deck (as explained in our previous post) and gethandcardcount. The gethandcardcount word is a "helper" word we created which uses a variable (#cardcount) to count how many cards are in a hand. It does this by starting at the address of the "hand" and looping through until it finds the first memory slot with the value of 0 which represents a blank card. Below is the code for the gethandcardcount word.
: gethandcardcount
0 #cardcount !
begin
dup
#cardcount @
swap
+
c@
0>
while
#cardcount @ 1 +
#cardcount !
repeat
#cardcount @
;


So, now that we have a word that draws a card from the deck we can use it as follows:
playerhand drawcard