Dave adds the necessary code to turn a demo into a playable game, complete with some rule variants.
In my last article, I started developing a simple card game called Acey-Deucey, in which you deal two cards face up, then bet on whether the next card is going to be between those two in rank value. In other words, if a 5 of diamonds and a jack of spades were flipped up, the bet would be whether the next card was going to be between a 6 and a 10.
I also dug into the math too, if you missed it, because this is a great game for understanding odds and probability. Remember, any given card has a 1 in 52 chance of appearing, and because two cards already have been exposed, that means any given card actually has 1:50 odds.
For the example above, there are four 6s, four 7s, 8s, 9s and 10s, meaning that there are (4*5):50 -> 20:50, 2:5 or a 40% chance that the next card flipped up will indeed be between the two exposed cards. Make that 5 of diamonds an ace of diamonds, and the odds get crazy good: 80%. I'd take those odds!
The math will factor into the script because you actually can have the game suggest what to do based on the odds. The greater the spread, the better the odds—easy enough.
I ended my last article with the game being able to shuffle and deal three cards: two exposed and one hidden. Running the program with just that code results in this:
$ sh acey-deucey.sh I've dealt: Ace of Hearts Queen of Diamonds $
There's not much to do yet, because there's no game logic, so let's add some.
To start, let's initialize and deal out the cards. With the highly mnemonic function names already assigned, it's quite readable:
initializeDeck shuffleDeck dealCards echo "Do you think the next card will be between? (y/n/q) " read answer
This is good for a start, but as I mentioned earlier with the math discussion, it can be a bit more helpful, particularly knowing that the dealCards function ensures that the two cards displayed are in order of increasing rank, which means that this is a darn helpful addition:
splitValue=$(( $rank2 - $rank1 ))
More important, it also means that the game can identify situations where there's no point in betting, like when a 7 of diamonds and 8 of clubs are dealt out. There are no cards that can be between them. This is added with a simple test:
if [ $splitValue -le 1 ] ; then echo "No point in betting when you can't win!" continue fi
The third card already has been “dealt” within the function dealCards, its rank calculated (as $rank3) and its display name set (as $cardname3). So, the test to see if the new card is or isn't between the two existing ranks is the next section of the code required, and it too is easy:
if [ $rank3 -gt $rank1 -a $rank3 -lt $rank2 ] ; then # winner! winner=1 else winner=0 fi
So you can pick three cards randomly out of the deck, you can calculate their ranks and display names, and you can prompt the user to guess whether the next card will or won't be between the two, then test to see if they were right.
What's left? Scoring. And, that's done with the $won variable, which is incremented in a conditional statement that appears immediately after the test to see if the third card is a $winner or not:
if [ $winner -eq 1 -a "$answer" = "y" ] ; then echo "You bet that it would be between the two and it is. You WIN!" won=$(( $won + 1 )) elif [ $winner -eq 0 -a "$answer" = "n" ] ; then echo "You bet that it would not be between the two and it isn't. You WIN!" won=$(( $won + 1 )) else echo "Bad betting strategy. You lose." fi
You'll notice that in this implementation of Acey-Deucey, I'm allowing the player to win if he or she bet the card won't be between the two, and it turns out that it isn't. This is probably too generous, because all you need to do is pick the more likely scenario, which is to say any situation where the spread is six cards or less (like at the very beginning of this article).
Still, it's not Vegas or Atlantic City, it's just a shell script, right? So I'll be nice. If you'd rather not offer that option, simply change the message in the first elif conditional code block and skip incrementing the $won variable.
All that's left to do is to wrap the entire code block in a big loop that'll run forever, and use that standard technique of shell script programmers worldwide:
while [ /bin/true ] ; do
You probably wondered why /bin/true existed in Linux, didn't you? So that's the first line of the main code block, and let's increment the $games variable in the last line of the block:
games=$(( games + 1 ))
But there's one more fragment needed, and that's the test to see if the user guessed that the third card would or would not be between the two displayed cards, or if the user quit the game. In the latter situation, it's time to display some stats. That's easy enough, and it turns out that you can just leave $answer alone for the yes/no answer:
if [ "$answer" = "q" ] ; then echo "You played $games games and won $won times." exit 0 fi
In fact, you'll never quit the game by falling out of the while loop, but that makes sense since the conditional test of /bin/true is, well, always true.
Stitch all these fragments together and you have a game, by George!
$ sh acey-deucey.sh I've dealt: 6 of Hearts 9 of Clubs The spread is 3. Do you think the next card will be between them? (y/n/q) n I picked: 9 of Hearts You bet that it would not be between the two and it isn't. You WIN! I've dealt: Ace of Hearts 7 of Spades The spread is 6. Do you think the next card will be between them? (y/n/q) y I picked: 3 of Spades You bet that it would be between the two and it is. You WIN! I've dealt: 7 of Spades 10 of Spades The spread is 3. Do you think the next card will be between them? (y/n/q) q You played 2 games and won 2 times. $
A perfect score. Nice. Las Vegas, here I come!