1. Technology
You can opt-out at any time. Please refer to our privacy policy for contact information.

Two Dimensional Arrays in Ruby

Representing the 2048 Game Board

By

See More About

The following article is part of a series.  For more articles in this series, see Cloning the Game 2048 in Ruby.  For the complete and final code, see the gist.

Now that we know how the algorithm will work, it's time to think about the data this algorithm will work on. There are two main choices here: a flat array of some kind, or a two dimensional array. Each have their advantages, but before we make a decision, we need to take something into account.

DRY Puzzles

A common technique in working with grid-based puzzles where you have to look for patterns like this is to write one version of the algorithm that works on the puzzle from left to right and then rotate the entire puzzle around four times. This way, the algorithm only has to be written once and it only has to work from left to right. This dramatically reduces the complexity and size of the hardest part of this project.

Since we'll be working on the puzzle from left to right, it makes sense to have the rows represented by arrays. When making a two dimensional array in Ruby (or, more accurately, how you want it to be addressed and what the data actually means), you have to decide whether you want a stack of rows (where each row of the grid is represented by an array) or a stack of columns (where each column is an array). Since we're working with rows, we'll choose rows.

How this 2D array is rotated, we'll get to after we actually construct such an array.

Constructing Two Dimensional Arrays

The Array.new method can take an argument defining the size of the array that you want. For example, Array.new(5) will create an array of 5 nil objects. The second argument gives you a default value, so Array.new(5, 0) will give you the array [0,0,0,0,0]. So how do you create a two dimensional array?

The wrong way, and the way I see people trying often is to say Array.new( 4, Array.new(4, 0) ). In other words, an array of 4 rows, each row being an array of 4 zeroes. And this appears to work at first. However, run the following code:


#!/usr/bin/env ruby
require 'pp'

a = Array.new( 4, Array.new(4, 0) )
a[0][0] = 1
pp a

It looks simple. Make a 4x4 array of zeroes, set the top-left element to 1. But print it and we get…


[[1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0]]

It set the entire first column to 1, what gives? When we made the arrays, the inner-most call to Array.new gets called first, making a single row. A single reference to this row is then duplicated 4 times to fill the outer-most array. Each row is then referencing the same array. Change one, change them all.

Instead, we need to use the third way of creating an array in Ruby. Instead of passing a value to the Array.new method, we pass a block. The block is executed every time the Array.new method needs a new value. So if you were to say Array.new(5) { gets.chomp }, Ruby will stop and ask for input 5 times. So all we need to do is just create a new array inside this block. So we end up with Array.new(4) { Array.new(4,0) }. Now let's try that test case again.


#!/usr/bin/env ruby
require 'pp'

a = Array.new(4) { Array.new(4, 0) }
a[0][0] = 1
pp a

And it does just as you'd expect.


[[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]

So even though Ruby doesn't have support for two dimensional arrays, we can still do what we need. Just remember that the top-level array holds references to the sub-arrays, and each sub-array should refer to a different array of values.

What this array represents is up to you. In our case, this array is laid out as rows. The first index is the row we're indexing, from top to bottom. To index the top row of the puzzle, we use a[0], to index the next row down we use a[1]. To index a specific tile in the second row, we use a[1][n]. However, if we had decided on columns… it would be the same thing. Ruby doesn't have any idea what we're doing with this data, and since it doesn't technically support two dimensional arrays, what we're doing here is a hack. Access it only by convention and everything will hold together. Forget what the data underneath is supposed to be doing and everything can fall apart real fast.

There's more!  To keep reading, see the next article in this series:  Rotating a Two Dimensional Array in Ruby

  1. About.com
  2. Technology
  3. Ruby
  4. Tutorials
  5. Writing a 2048 Clone in Ruby
  6. Two Dimensional Arrays in Ruby

©2014 About.com. All rights reserved.