Work the Shell: Creating the Concentration Game PAIRS with Bash

Exploring the nuances of writing a pair-matching memory game and one-dimensional arrays in Bash. By Dave Taylor

I've always been a fan of Rudyard Kipling. He wrote some great novels and stories, mostly about British colonial-era India. Politically correct in our modern times? Not so much, but still, his books are good fun for readers and still are considered great literature of its time. His works include The Jungle Book, Captains Courageous, The Just So Stories and The Man Who Would Be King, among many others.

He also wrote a great spy novel about a young English boy who is raised as an Indian native and thence recruited by the British government as a spy. The boy's name is the title of the book: Kim. In the story, Kim is trained to have an eidetic memory with a memory game that involves 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.

For some reason, that scene has always stuck with me, and I've even tried to teach my children to be situationally aware through similar games like "Close your eyes. Now, what color was the car that just passed us?" Since most of us are terrible observers (see, for example, how conflicting eyewitness accident reports can be), it's undoubtedly good practice for general observations about life.

Although it's tempting to try to duplicate this memory game as a program, the reality is that with just a shell script, it would be difficult. Perhaps you display a random pattern of letters and digits in a grid, then clear the screen, then ask the user to enter patterns, but that's really much more of a game for a screen-oriented, graphical application—not shell scripts.

But, there's a simplified version of this that you can play with a deck of cards: Concentration. You've probably played it yourself at some point in your life. You place the cards face down in a grid and then flip up two at a time to try to find pairs. At the beginning, it's just random guessing, but as the game proceeds, it becomes more about your spatial memory, and by the end, good players know what just about every unflipped card is at the beginning of their turn.

Designing PAIRS

That, of course, you can duplicate as a shell script, and since it is going to be a shell script, you also can make the number of pairs variable. Let's call this game PAIRS.

As a minimum, let's go with four pairs, which should make debugging easy. Since there's no real benefit to duplicating playing card values, it's just as easy to use letters, which means a max of 26 pairs, or 52 slots. Not every value is going to produce a proper spread or grid, but if you aim for 13 per line, players then can play with anywhere from 1–4 lines of possibilities.

Playability would be enhanced by having clickable spots, but this is a shell script, dude, so you'll have to suffer through a Chess-style grid notation: line,slot.

A display could look something like this mockup:


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

The letter A is at 2,4, and the letter E is at 3,9. Even better though, because the first value should only ever be 1–4, you could omit the comma to make things more succinct, making the A at 24 and the E at 39. There'll be three-digit values too, like the Z at 413, but that's still easy to pull apart and process.

Actually, now that there's a 13x4 grid pattern, let's rethink the flexibility of the game. Instead of allowing an arbitrary number of pairs, let's make the game less flexible and constrain users to being able to specify only how many rows of 13 they want. Even more so, only even numbers of rows will be acceptable. (Obviously with a single row of 13, it'd be hard to have only pairs show up!)

Data Representation

Bash has decent support for arrays, so my first inclination is to make this a linear array and simply divide by 13, as needed to convert "display" coordinates to actual array coordinates. A multi-dimensional array would be better, but Bash doesn't offer any support for that particular data structure. There's a workaround by using an associative array, but that requires Bash 4.x, and MacOS X still ships with Bash 3.x, which means a lot of our readers (shhh, I know not all of you are full-time Linux folk) would be left out in the cold. Ah, fun, fun.

Enough chat though, let's get to the coding side of things, although I'll have to continue the development in my next column.

Coding the Grid

A reasonable place to start this whole project is by looking at how to work with a one-dimensional array in Bash. An array slot is assigned a value like this:


myArray[2]=value

And it's referenced in the slightly clumsy format:


echo ${myArray[2]}

Bash wants you to use the declare statement to identify arrays prior to use—declare -a myArray—and you can assign initial values like this:


declare -a myArray=(cat dog pig frog snake)

There are some handy shortcuts with @ and * to learn about, but let's just start coding.

First things first, here's the Bash function I've written to initialize the array with A–Z values (I'll shuffle the values in a different function):


initialize ()
{
   # initialize the board with sequential letters

   count=1   maxcount=$1

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

     for slot in {1..13}
     do
       index=$(( $addon + $slot ))
       letter=$(( $index % 26 ))
       board[ $index ]=${letters[$letter]}
     done
     count=$(( $count + 1 ))
   done
}

While it may appear that there's lots to consider here, the heart of the function is the for loop within a while loop. The while steps through rows, and the for loop steps through the 13 slots for each row.

The most interesting line might be this:


board[ $index ]=${letters[$letter]}

The value of a given slot in the board array is going to be set to the $letter index value of the $letters array. That array is set in the opening declarations:


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)

The result is interesting, because Bash really wants to have 0-based indexing, and here I'm using it all as 1-based indexing instead. But, that I shall show you in my next article, the continuation of the PAIRS game-programming adventure!

About the Author

Dave Taylor has been hacking shell scripts on UNIX and Linux systems for a really long time. He's the author of Learning Unix for Mac OS X and Wicked Cool Shell Scripts. You can find him on Twitter as @DaveTaylor, and you can reach him through his tech Q&A site: Ask Dave Taylor.

Dave Taylor