# Work the Shell: Creating the Concentration Game PAIRS with Bash, Part II

## Dave finishes up the PAIRS concentration game, only to realize it's too hard to solve! By Dave Taylor

In my last article, I tossed away my PC card and talked about how I was a fan of the British colonial-era writer Rudyard Kipling. With that in mind, I do appreciate that you're still reading my column.

I was discussing the memory game that the British spy plays with the orphan boy Kim in the book of the same name. The game in question involves Kim being shown a tray of stones of various shapes, sizes and colors. Then it's hidden, and he has to recite as many patterns as he can recall.

The card game Concentration is clearly inspired by the same pattern memorization game, and it's considerably easier to set up: shuffle a deck of cards, place them face down in a grid, then flip pairs to find matches. In the beginning, it's just guessing, of course, but as the game proceeds, it becomes more about spatial memory than luck. Someone with an eidetic memory always will win.

Using letters makes things easy, so I suggested a row, column, notational convention like this:

``````
1   2   3   4   5   6   7   8   9   10  11  12  13
1: [-] [-] [-] [-] [-] [-] [-] [-] [-] [-] [-] [-] [-]
2: [-] [-] [-] [A] [-] [-] [-] [-] [-] [-] [-] [-] [-]
3: [-] [-] [-] [-] [-] [-] [-] [-] [E] [-] [-] [-] [-]
4: [-] [-] [-] [-] [-] [-] [-] [-] [-] [-] [-] [-] [Z]
```
```

You can represent uppercase letters as a shell array like this:

``````
declare -a letters=(A B C D E F G H I J K L M N O P Q R
S T U V W X Y Z)
```
```

Unfortunately, Bash doesn't support multidimensional arrays, so you're going to have to represent the grid as a one-dimensional array. It's not too hard though, because the grid is straightforward. Here's an index formula if `firstvalue` is the first digit and `rest` is the remainder of the index value:

``````
index=\$(( ( ( \$firstvalue - 1 ) * 13 ) + \$rest ))
```
```

The letter "E" in the above grid, at 3,9, would show up in the array as ((3-1)*13)+9 or slot 35.

### Shuffle Those Values

The script from my last article already initializes everything in sequential order and defaults to 2 * 13 slots (for simplicity in debugging). The work of the script is really in the shuffle, but it turns out that there's a pretty elegant little shuffle algorithm (shown in a kind of sloppy C for illustrative purposes) floating around the internet that can be tapped for this task:

``````
shuffle {
for (i = n-1; i > 0; i-) {
int j = rand() % (i+1);
swap( array[i], array[j]);
}
}
```
```

Translating this into a shell script and using better variable names, here's what I created:

``````
shuffle ()
{
# shuffle board with \$1 * 13 values

totalvalues=\$(( \$1 * 13 ))

index=\$totalvalues

while [ \$index -gt 1 ] ; do

randval=\$(( ( \$RANDOM % \$index ) + 1 ))

# swapping value pair

temp=\${board[\$index]}
board[\$index]=\${board[\$randval]}
board[\$randval]=\$temp

index=\$(( \$index - 1 ))
done
}
```
```

Instead of having a separate function for the value swap, I just went ahead and dropped that into the function itself. It's faster and also lets you sidestep the dereference hassle neatly.

Here's what happens when you initialize the grid, shuffle it, then display it on screen (and yes, I changed the "[]" to "<>" to make it more visually interesting):

``````
1   2   3   4   5   6   7   8   9   10  11  12  13
1: <V> <X> <M> <R> <C> <F> <K> <O> <U> <H>
<T> <Q> <L>
2: <A> <G> <B> <N> <J> <Y> <P> <W> <Z> <E>
<D> <I> <S>
```
```

Of course, 26 grid spots equals exactly the number of letters in the alphabet, so there are exactly zero pairs. That's not much fun as games go, but what if you request a four-line grid?

``````
1   2   3   4   5   6   7   8   9   10  11  12  13
1: <G> <J> <A> <K> <P> <L> <B> <O> <I> <X>
<Y> <N> <F>
2: <Y> <C> <Z> <O> <G> <D> <T> <N> <V> <D>
<H> <E> <U>
3: <W> <C> <R> <Q> <M> <B> <E> <K> <F> <I>
<T> <Q> <R>
4: <U> <Z> <P> <H> <S> <W> <L> <J> <M> <X>
<V> <S> <A>
```
```

A few pairs jump out, like 2,13 and 4,1 for the "U" values, but remember, the game is going to hide all of this, and it's your job to guess these pairs.

Ah, it's suddenly not so easy, eh?

### Tracking What's Been Guessed

Now that the grid is being created and shuffled correctly, the next step is to differentiate between spots that have been guessed correctly and those that are still unknown. You could do that with a parallel array, but why go through the hassle? Instead, initialize every value to have a dash as its first character and then remove it once guessed.

The display function now can test a value to see if it's a "negative" letter. If so, it'll display a "-" instead of the actual value. Now the initial grid looks like this:

``````
1   2   3   4   5   6   7   8   9   10  11  12  13
1: <-> <-> <-> <-> <-> <-> <-> <-> <-> <->
<-> <-> <->
2: <-> <-> <-> <-> <-> <-> <-> <-> <-> <->
<-> <-> <->
3: <-> <-> <-> <-> <-> <-> <-> <-> <-> <->
<-> <-> <->
4: <-> <-> <-> <-> <-> <-> <-> <-> <-> <->
<-> <-> <->
```
```

What about entering your guess for the location of a given pair? I'm going to make things harder by not showing the values in a grid but rather just displaying them directly.

Enter a grid value as row, col, then split it into those values and multiply it out to a unique array index. It's complicated, but if you read `\$slot1` and `\$slot2` as the input values from the user, the analysis loop is this:

``````
row1=\$( echo \$slot1 | cut -c1 )
col1=\$( echo \$slot1 | cut -d, -f2 )
row2=\$( echo \$slot2 | cut -c1 )
col2=\$( echo \$slot2 | cut -d, -f2 )

index1=\$(( ( \$row1 - 1) * 13 + \$col1 ))
index2=\$(( ( \$row2 - 1) * 13 + \$col2 ))

val1=\${board[\$index1]}
val2=\${board[\$index2]}
```
```

There's a woeful lack of error-checking here, but that's something I like to add afterward, once I get the core algorithm functional.

Armed with `\$val1` and `\$val2` above, testing to see if you have a match is easy:

``````
if [ \$val1 = \$val2 ] ; then
echo "You've got a match. Nicely done!"
board[\$index1]=\${val1:1:1}
board[\$index1]=\${val2:1:1}
unsolved=\$(( \$unsolved - 2 ))
else
echo "No match. \$row1,\$col1 = \${val1:1:1} and \
\$row2,\$col2 = \${val2:1:1}."
fi
```
```

Did you notice `\$unsolved` in the matching conditional code? That's how you can keep track of whether the grid has been solved.

### So Let's Give It a Try!

With all this code in place, let's give it a whirl:

``````
Welcome to PAIRS. Your mission is to identify matching letters
in the grid. Good luck. If you give up at any point, just use
q to quit.

Enter a pair of values in row,col format : 1,1 4,1
No match, but 1,1 = P and 4,1 = A.

1   2   3   4   5   6   7   8   9   10  11  12  13
1: <-> <-> <-> <-> <-> <-> <-> <-> <-> <->
<-> <-> <->
2: <-> <-> <-> <-> <-> <-> <-> <-> <-> <->
<-> <-> <->
3: <-> <-> <-> <-> <-> <-> <-> <-> <-> <->
<-> <-> <->
4: <-> <-> <-> <-> <-> <-> <-> <-> <-> <->
<-> <-> <->

Enter a pair of values in row,col format : 2,1 3,1
No match, but 2,1 = Z and 3,1 = B.

1   2   3   4   5   6   7   8   9   10  11  12  13
1: <-> <-> <-> <-> <-> <-> <-> <-> <-> <->
<-> <-> <->
2: <-> <-> <-> <-> <-> <-> <-> <-> <-> <->
<-> <-> <->
3: <-> <-> <-> <-> <-> <-> <-> <-> <-> <->
<-> <-> <->
4: <-> <-> <-> <-> <-> <-> <-> <-> <-> <->
<-> <-> <->
```
```

I'm basically done with the program at this point, and I'm realizing something. This is really hard to solve as a game.

### Hacks and Mods

Here's an exercise for you, dear reader: this is generating 26 possible values, the letters A–Z, which requires a minimum grid of 52 slots. That's a lot! Modify it to work with single digits, and then adjust the grid dimensions appropriately. For example, 20 slots can be portrayed in a 4 x 5 grid. For sure, 19 possibilities for the match of a revealed value is a lot easier than 51 possibilities.

Have fun with this, and grab the full script below or from here. Let me know how you modify this to make it more entertaining, interesting or just make it easier!

### The Full Script

``````
#!/bin/sh

# PAIR - a simple matching game, implmemented as a linear array

# Usage: PAIR rowcount
#    note: rowcount must be even and specifies how many 13-slot
#    rows are created
#    the default value is 2 rows of 13 values

declare -a board
declare -a letters=(A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)
rows=4			# default # of 13 slot rows

initialize ()
{
# given number of rows, initialize the board with all blank values

count=1   maxcount=\$1

while [ \$count -le \$maxcount ]
do
addon=\$(( 13 * ( \$count - 1 ) ))

for slot in {1..13}
do
letter=\$(( \$index % 26 )) 	# unshuffled value

done
count=\$(( \$count + 1 ))
done
}

shuffle ()
{
# given the board[] array with \$1 * 13 entries, shuffle the contents

totalvalues=\$(( \$1 * 13 ))

index=\$totalvalues

while [ \$index -gt 1 ] ; do

randval=\$(( ( \$RANDOM % \$index ) + 1 ))

# swapping value pair

temp=\${board[\$index]}
board[\$index]=\${board[\$randval]}
board[\$randval]=\$temp

index=\$(( \$index - 1 ))
done
}

showgrid ()
{
# show our grid. This means we need to display \$1 x 13 slots with
# labels
#     rows is grabbed from the global var 'rows'

count=1

echo "    1   2   3   4   5   6   7   8   9   10  11  12  13"

while [ \$count -le \$rows ]
do
/bin/echo -n "\$count: "
addon=\$(( 13 * ( \$count -1 ) ))

for slot in {1..13}
do
value=\${board[\$index]}
if [ \${value:0:1} != '-' ] ; then   # matched!
/bin/echo -n "<\${board[\$index]}> "
else
/bin/echo -n "<-> "     # still unknown
fi
done
echo ""
count=\$(( \$count + 1 ))
done
}

##################################

if [ \$# -gt 0 ] ; then
rows=\$1
fi

if [ \$(( \$rows % 4 )) -ne 0 ] ; then
echo "Ooops! Please only specify a multiple of 4 as the number
of rows (4, 8, 12, etc)"
exit 1
fi

unsolved=\$(( \$rows * 13 ))
maxvalues=\$unsolved		# parameter testing

echo "Welcome to PAIRS. Your mission is to identify matching letters
in the grid."
echo "Good luck. If you give up at any point, just use q to quit."
echo ""

initialize \$rows

shuffle \$rows

showgrid

while [ \$unsolved -gt 0 ] ; do
echo ""
/bin/echo -n "Enter a pair of values in row,col format : "

if [ "\$slot1" = "" -o "\$slot2" = "" ] ; then
echo "bye."
exit 1
fi

row1=\$( echo \$slot1 | cut -c1 )
col1=\$( echo \$slot1 | cut -d, -f2 )
row2=\$( echo \$slot2 | cut -c1 )
col2=\$( echo \$slot2 | cut -d, -f2 )

index1=\$(( ( \$row1 - 1) * 13 + \$col1 ))
index2=\$(( ( \$row2 - 1) * 13 + \$col2 ))

if [ \$index1 -lt 0 -o \$index1 -gt \$maxvalues -o \$index2 -lt
↪0 -o \$index2 -gt \$maxvalues ] ; then
echo "bad input, not a valid value"
exit 1
fi

val1=\${board[\$index1]}
val2=\${board[\$index2]}

if [ \$val1 = \$val2 ] ; then
echo "You've got a match. Nicely done!"
board[\$index1]=\${val1:1:1}
board[\$index1]=\${val2:1:1}
unsolved=\$(( \$unsolved - 2 ))
else
echo "No match, but \$row1,\$col1 = \${val1:1:1} and \$row2,\$col2 =
↪\${val2:1:1}."
fi

echo ""
showgrid

done

exit 0
```
```