BASH Card Game

Jarret B

Well-Known Member
Staff member
Joined
May 22, 2017
Messages
344
Reaction score
377
Credits
11,920
BASH can be a very useful tool when in a terminal of Linux (for those that use BASH). When people ask how to learn a language for programming, scripting can be no different, my answer is to write a game.

Writing a game can keep people interested in writing something and complete it. It can help make people want to learn, since they see it as fun.

The completed script is below the article for download. Just do a 'chmod +x GoFish.sh' and then run it with the command './GoFish.sh'. I'll go over each section to explain what is happening, but I'm not getting too deep in the coding, at least not each line.

Main Code

The beginning of the script comprises the functions and calls for specific coding. Towards the end is:

# Start of Executing Script
declare -u hand="" # cards in the user's hand
declare -i usercount=0 # number of cards in the user's hand
declare -i pccount=0 # number of cards in the PC's hand
declare -a pool # array of cards in the Pool
declare -a pc # array of cards in the PC's hand
declare -a user # array of cards in the user's hand
declare -i matches # number of matches found for the specific hand
declare -i card1=0 # variable for the contents of a single card
declare -i quit=0 # whether the player has selected to quit the game
declare -i found=0 # whether a guessed card is a valid guess


Here, I am just declaring some global variables. The variables are in the main portion of the code and also in the functions. The value is accessible from all parts of the code, whether or not in a function.

The next function call is to initialize the deck and clear out the array for the hand of the user and PC.

initializeCards


The contents of the function are:

function initializeCards {
# Set up the initial deck and clear out the User and PC arrays
count=1
while [ $count -le 52 ]
do
card[$count]=$count
user[$count]=""
pc[$count]=""
((count+=1))
done

card[0]=0
user[0]=0
pc[0]=0
}


We perform a loop to count from 1 to 52. Then set the array, called 'card[]', to the value of the counter. We also set the arrays 'user[]' and 'pc[]' to NULL to make sure we cleared it out. At the end, I set the first array element to '0'. I'll explain this in a bit when it'll make more sense.

You may wonder why the array is just holding the numbers 1 to 52. Why do we not fill it with card values? Browsing the Internet showed me this method, which is really a great way to handle cards. I'll explain this in the function of 'showCards'. For now, just realize we set the array and the arrays holding the cards for the players are cleared.

The next function called is the 'shuffleCards' function.

function shuffleCards {
# Shuffle the cards
shuffleTimes=$(( ( SRANDOM % 10) + 1 ))
shuffleCount=1
while [ $shuffleCount -le $shuffleTimes ]
do
count=1
total=52
swap[0]=0
card[0]=0
while [ $count -le 52 ]
do
pick=$(( ( SRANDOM % $total) + 1 ))
swap[$count]="${card[$pick]}"
unset card[$pick]
card=( "${card[@]}" )
((count+=1))
((total-=1))
done
card=("${swap[@]}")
((shuffleCount+=1))
done
}

Imagine shuffling a deck of cards. You take a portion of the cards and mix it into the other portion of the cards. You do this a random number of times before you deal.

So, the line 'shuffleTimes=$(( ( SRANDOM % 10) + 1 ))' selects a random number of times to shuffle the deck from 1 to 10. We could just as easily make the line 'shuffleTimes=$(( ( SRANDOM % 10) + 5 ))' to get a random number between 5 and 15. We then have a variable named 'shufflecount' set to '1'. This will let us count the number of shuffles from 1 to 'shuffleTimes'. We then have a loop to run untile 'shuffleCount' is equal to 'shuffleTimes'.

Here, we set 'count' to '1'. The count is used to switch all 52 cards into a different order. The 'total' shows the number of remaining cards to be moved, in this case '52'. We have an array named 'swap[]' that will hold the shuffled cards in a new order. The array 'card[]' holds our cards right now.

The next loop will basically loop 52 times. The variable 'count' is '1' and increases by '1' each time we perform the loop until it is equal to '52'.

Inside the loop, we choose a random number between 1 and the variable 'total'. We place this number into 'pick'. We then move the value of the card 'pick' from 'card[$pick]' to the next slot in the 'swap[]' array, which is 'count'.

The next command is 'unset card[$pick]'. The command will remove the picked card from the 'card[]' array. What occurs is that the value in the element is NULL. Since we picked the card, we cannot pick it again.

The next line, 'card=( "${card[@]}" )' will take the contents of the 'card[]' array and copy it back into the 'card[]' array. What happens when it does, it moves all the NULL values to the end of the array. All the elements still containing a card value move to the beginning. For example, if it picks '6', it places the sixth card in the first element of 'swap[]'. The sixth card is Null and then moved to the end of the array, or element '52'. Now we increment 'count' by 1, so the next picked card goes into slot 2 and so on. The 'total' decreases by '1' because we have one less card to shuffle. It performs the loop until it moved all 52 cards to 'swap[]'. Outside of the loop, we move the completed 'swap[]' array back to the 'card[]' array. Then we add '1' to the 'shuffleCount' and continue until we complete shuffling.

Now, I can explain the use for the setting the '0' element of an array to '0'. When we copy an array back to an array, the element '0' is really the first element. If we leave it empty, then it is NULL. When we copy the array, all the cards will shift down and it places one card into element '0'. In this way, we lose a card since we are dealing with the elements 1 to 52 and not 0 to 51.

The next function call is to 'dealCards'. The function is:

function dealCards {
# Deal 7 cards to the PC and User, then place the rest of the cards in the Pool
count=1
deckcount=52
poolcount=0
usercount=0
pccount=0
# Deal 7 cards to each of the two players
while [ $count -le 7 ]
do
user[$count]=${card[1]}
pc[$count]=${card[2]}
unset card[1]
unset card[2]
card=( "${card[@]}" )
((usercount+=1))
((pccount+=1))
((deckcount-=2))
((count+=1))
done
# The rest of the cards go in the pool
poolcount=0
for count in $(seq 1 $deckcount)
do
((poolcount+=1))
pool[$poolcount]=${card[$count]}
((deckcount-=1))
done
}


The initial variables we covered, so I'll skip those. We are going to use 'count' to count out seven cards for the two players. Within the loop, we give the top card, card '1', to the user. Then the second card, 'card[2]' goes to the PC. The cards go into the element for the array 'user[]' or 'pc[]' as needed. Then the cards are unset and reordered so that 'card[1]' is on top again. The variables, 'usercount' and 'pccount', determine how many cards are in each hand. I keep a count of the number of cards on the deck in the variable 'deckcount'. So each player gets seven cards.

We then put the remaining cards into the Pool. We perform a loop with a count from '1' to 'deckcount', in this case it is 1 to 38. It puts the value in the specified element into the next element in the 'pool[]' array and increments the 'poolcount'.

We have created the deck, shuffled the deck and dealt the cards. We are now ready to play.

Let's look at how we start the play before we get into the loop of taking turns.

# Start game play
showHand
removeUserMatches
removePCMatches
echo "PC has $pccount cards left"
showHand


Since we have dealt the cards, we have our cards, but we need to create a visual representation of the cards for the user. To generate the values, we call 'showHand'.

function showHand {
# Show the cards in the users hand
count=1
hand=""
while [ $count -le $usercount ]
do
card1=${user[$count]}
value=$(getrank "$card1")
suit=$(( ( ( $card1 % 4 ) ) ))
case $suit in
0)
suit="♥";;
1)
suit="♦";;
2)
suit="♣";;
3)
suit="♠";;
esac
hand="${hand} ${value}${suit}"
((count+=1))
done
echo "Your Hand: $hand"
}


There is a counter, as always, called 'count'. We set our variable, 'hand', to NULL that holds the card values of our card hand.

We perform a loop to go through all our cards, 1 to '$usercount'.

First, we get the value of the card in the array, 'user[]' and place it into the variable 'card1'. We then call a set of code called 'getrank', passing the value in 'card1' and returning the face value to the variable 'value'. I'll get to the code soon. Let's finish this function first.

The next line takes the value, in 'card1', and divides it by 4. It places the remainder in the value of 'suit'. Let's say the card value was '39'. Remember, the card values are '1' to '52'. So, to divide '39' by 4 will give a remainder of '3'. The remainders are always '0', '1', '2' and '3'. These values will determine the suit of the card, as shown in the 'case' statement. A remainder of '0' is 'hearts', '1' is 'diamonds', '2' is 'clubs' and '3' is 'spades'. We can change these however you may want. The order doesn't matter.

Now, I'm sure you are wondering how I got the card characters. You need to fund the UTF value for the characters you want. Go to 'https://unicodelookup.com/' and in the search box, type 'heart'. You should get a result of a few different types. For mine, I used 2665 for the heart. To generate the heart icon, you need to press CTRL+SHIFT+U. Once pressed, you’ll see an underlined 'u' appear. Type the UTF-8 code, in this case 2665, and press enter. The proper icon should appear. The diamond was 2666, the club was 2663, and the spade was 2660.

The 'getrank' code does not have the ‘function’ label, so you can set a variable equal to it. At the end of the code for 'getrank', just use the command 'return <variable>' to return a value.

The code allows a visual representation of the cards for the user.

Let's look at the 'getRank' code:

getrank() {
# Get card's face value
card1=$1
rank=$(( ( ( $card1 - 1 ) / 4 ) + 1 ))
if [ $rank -ge 2 ] && [ $rank -le 9 ]
then
value=$rank
elif [ $rank -eq 1 ]
then
value="A"
elif [ $rank -eq 10 ]
then
value="T"
elif [ $rank -eq 11 ]
then
value="J"
elif [ $rank -eq 12 ]
then
value="Q"
elif [ $rank -eq 13 ]
then
value="K"
fi
echo $value
}


Since we are passing a value to the code, $1 will represent it', and we place the value into the variable 'card1'. We take the value of the card, that we passed to it, and subtract 1, then divide by 4, then add 1 back. The value should be a whole number '1' to '13'. These represent the thirteen values of the cards, which are Ace through King. We hit an if statement that leaves the values from 2 through 9. If the value is a '1' it changes to an 'A' (Ace), 10 is a 'T', 11 is 'J' (Jack), 12 is a 'Q' (Queen) and 13 is 'K' (King). It then echoes the value back, which is placed into the variable used to set equal to called code with the card value included. For example, 'value=$(getrank "$card1")' will echo the value from the code and save it to the variable 'value'.

After we display the hand that was dealt to the user, we call two functions that operate the same. The first function ' removeUserMatches' will find duplicate cards in the users’ hand, then remove them, and 'removePCMatches' to find and remove duplicate cards in the computer's hand.

The program then echoes a line to let the user know the number of cards left in the PC's hand. Since we remove duplicates, we show what is remaining in the user's hand. Now we are ready to take turns and ask the other player if they have a card to match one we have and vice versa.

Taking Turns

We perform a 'while loop' until the user or PC has zero cards left, or if the user chooses 'x' to exit the game.

while ([ ${usercount} -gt 0 ] && [ ${pccount} -gt 0 ]) # && [ $quit = 0 ]
do
found=0
echo "--------------------------------"
# loop begins here
while [ $found -eq "0" ]
do
valid=0
while [[ $valid -eq "0" ]]
do
read -n1 -p "Your turn. What do you want to ask for (x to exit)? " inp
inp=${inp^^}
if [[ "A23456789TJQKX" == "$inp" ]]
then
#good input
valid=1
else
#bad input
valid=0
echo ""
echo "** Invalid card!"
fi
done
echo ""
if [ $inp == "X" ]
then
quit=1
echo "You Quit!"
exit 0
fi
# check if inp is a valid card in hand
found=$(checkinp "$inp")
if [ $found -eq "0" ]
then
echo "You do not have that card to guess."
else
# remove guessed card from computers hand, move to users hand and remove
# check for card in PC hand
match=0
echo "Check opponents hand"
for i in $(seq 1 $pccount);
do
rank=${pc[$i]}
rank1=$(getrank "$rank")
if [ $rank1 == $inp ]
then
echo "Card found in opponents hand"
match=1
((usercount+=1))
user[$usercount]=${pc[$i]}
unset pc
user[0]=0
pc[0]=0
pc=( "${pc[@]}" )
user=( "${user[@]}" )
((pccount-=1 ))
i=$pccount
break
fi
done
if [ $match -eq "0" ]
then
echo "No matches found, so Go Fish!"
# Draw Random Card from Pool, add to hand and check for matches.
draw=$(( ( SRANDOM % $poolcount) + 1 ))
((usercount+=1))
user[$usercount]=${pool[$draw]}
unset pool[$draw]
user[0]=0
pool[0]=0
pool=( "${pool[@]}" )
user=( "${user[@]}" )
((poolcount-=1 ))
rank=${user[$usercount]}
rank1=$(getrank "$rank")
echo "You drew an $rank1"
fi
fi
done
removeUserMatches
showHand
checkWin


The line 'if [[ "A23456789TJQKX" == "$inp" ]]' lists all the valid input, the cards 'Ace' through 'King' or an 'X'. It checks that the value of '$inp' is one of the valid choices. If not, it alerts the user that it is an invalid choice and lets them choose again. If the choice is a valid card, we then need to check that the user guessed a card they are holding. The line 'found=$(checkinp "$inp")' uses the 'checkinp' function to return a value of '1' if the card is in the user's hand. If it returns a '1', then it searches the PC's hand to see if they have the card. If they do, it moves the card to the user's hand. Counts for the user, 'usercount', and the PC, 'pccount', are adjusted accordingly. If it did not find the card in the PC's hand, then they are told to 'Go Fish'. It selects a random card from the Pool and added to the user's hand. Whether the card came from the Pool or the PC, it checks the user's hand for matches, 'removeUserMatches'. Since the removal of cards may occur, it regenerates the visual hand and displays it.

The function 'checkWin' is called to see if either player has a card count of 0 to signify they win.

Once the user has had a turn, the PC gets a turn unless a player has won.

The PC code is:

# Let user take a turn. Randomly pick a card in the PCs hand and guess. Remove if found.
# or draw from pool
echo "*******************"
guess=$(( ( SRANDOM % $pccount) + 1 ))
rank=${pc[$guess]}
inp=$(getrank "$rank")
echo "PC guesses $inp"
# remove guessed card from computers hand, move to users hand and remove
# check for card in User's hand
match=0
echo "Check User's hand"
for i in $(seq 1 $usercount);
do
rank=${user[$i]}
rank1=$(getrank "$rank")
# echo "$rank1 $inp"
if [ $rank1 == $inp ]
then
echo "Card found in User's hand"
match=1
((pccount+=1))
pc[$pccount]=${user[$i]}
unset user
user[0]=0
pc[0]=0
pc=( "${pc[@]}" )
user=( "${user[@]}" )
((usercount-=1 ))
i=$usercount
break
fi
done
if [ $match -eq "0" ]
then
echo "No matches found, so PC must Go Fish!"
# Draw Random Card from Pool, add to hand and check for matches.
draw=$(( ( SRANDOM % $poolcount) + 1 ))
((pccount+=1))
pc[$pccount]=${pool[$draw]}
unset pool[$draw]
pc[0]=0
pool[0]=0
pool=( "${pool[@]}" )
pc=( "${pc[@]}" )
((poolcount-=1 ))
fi
removePCMatches
echo "PC has $pccount cards left"
checkWin
showHand
done


It chooses a random card from the hand. The PC asks for the card and it searches the user's hand. If it finds a match, it moves the card to the PC's hand and removed from the User's hand. If it finds no match, the PC will Go Fish. The script picks a random card from the Pool. It adds the card to the PC's hand and remove from the Pool. It changes counts as needed. The function 'removePCMatches' is called to check for duplicates to remove. It prints a line to show how many cards remain in the PC's hand. The code checks to see if either player has zero cards to signify a winner. If no one won, it displays the User's hand again for them to make their next guess and the game continues.

Conclusion

There is a lot of coding for this program, but it is quite repetitive since both players have to perform the same actions.

If you want to learn BASH scripting, look over the previous articles to get a good basis for the coding. Then to reinforce the articles, look over the script for Go Fish.
 

Attachments

  • GoFish.txt
    8.6 KB · Views: 285

Members online


Top