Boggle in Ruby: Dice and Grids

Harry Turnbull
9 min readFeb 27, 2020

--

I thought I’d make a simple game based on the popular word game Boggle.

The project is available on GitHub, so why not check it out and have a game?

A boggle board: a four by four grid of letters
By an amazing coincidence this Boggle grid includes the word “blog”. Credit: Rich Brooks/Flickr

Specifications

If we’re to make a Boggle like game in Ruby, first we’ll need to break down the game into a series of steps that we might be able to do in the program. Here’s how a game of Boggle goes down:

  • 16 letters in a 4x4 grid are presented to the players.
  • A timer is set for three minutes.
  • Players must find words in the grid and write them down in a list.
  • After the three minutes are up, players check each of their words to see if they are valid. Here are the rules:
    a. The word must be at least 3 letters long.
    b. The word must be an actual word, in the dictionary.
    c. The letters of the word must be in a chain through the grid, i.e. start at the first letter, and the second letter must be immediately adjacent in any of the surrounding 8 cells, and so on for the third letter.
    d. No cell can be used twice in a word.
  • If the word abides by all the rules, it scores points based on its length, which are added to that player’s total:
    3 Letters: 1 point
    4 Letters: 1 point
    5 Letters: 2 points
    6 Letters: 3 points
    7 Letters: 4 points
    8 or more Letters: 11 points

Now that we know how Boggle works, here are a few things that would be good to include to help the player:

  • Display a list of the words the user has already entered.
  • Display the grid so the user can always see it.
  • After the game, provide the player with a report of their words, and indicate if they were valid.
  • Provide a list of words they could’ve got.

Today’s task

That sure is a lot! For today let’s focus on setting up the grid by rolling the dice, and showing that to the player.

Just a note before we begin, all the examples of the grid are going to be the same for consistency in the explanations of what’s going on, but rest assured, when the code is run it will jumble up the dice.

Rolling the dice

We could set up the grid just by setting each element in a 2d array to a random letter of the alphabet, however in real life, the Boggle grid is set by rolling 16 6-sided dice, each with a single letter on each face (apart from “Qu”, which goes on a single face).

I’m sure the configuration of letters on the dice has been carefully thought out so you have enough vowels and the like, so let’s see how we can emulate this.

Here’s an 2d array containing representations of all 16 dice, and in each dice, the six faces (notice the “Qu”):

dice = [
["R", "I", "F", "O", "B", "X"],
["I", "F", "E", "H", "E", "Y"],
["D", "E", "N", "O", "W", "S"],
["U", "T", "O", "K", "N", "D"],
["H", "M", "S", "R", "A", "O"],
["L", "U", "P", "E", "T", "S"],
["A", "C", "I", "T", "O", "A"],
["Y", "L", "G", "K", "U", "E"],
["Qu", "B", "M", "J", "O", "A"],
["E", "H", "I", "S", "P", "N"],
["V", "E", "T", "I", "G", "N"],
["B", "A", "L", "I", "Y", "T"],
["E", "Z", "A", "V", "N", "D"],
["R", "A", "L", "E", "S", "C"],
["U", "W", "I", "L", "R", "G"],
["P", "A", "C", "E", "M", "D"],
]

Now to “roll” all the “dice”, and get that into a new array:

rolls = dice.map do |die|
die[rand(6)]
end

What this does is map through each of the elements of the dice array(die), and for each die return a random face, which is then added onto a new array representing the grid.

Let’s snazz it up a bit:

rolls = dice.shuffle.map do |die|
die.sample
end
p rolls
# => ["F", "U", "S", "D", "S", "K", "E", "P", "R", "O", "O", "V", "I", "Qu", "U", "H"]

Just before mapping the dice, it is scrambled with the shuffle method so the dice don’t always appear in the same order. Also, rather getting a random number from 0 to 5 and then accessing the die array at that index, we can just use the sample method.

Making the grid

Right now, rolls is a list, not a grid, so let’s convert it into a 2d array:

grid = []
grid.push(rolls[0,4])
grid.push(rolls[4,4])
grid.push(rolls[8,4])
grid.push(rolls[12,4])
p grid
# => [["F", "U", "S", "D"], ["S", "K", "E", "P"], ["R", "O", "O", "V"], ["I", "Qu", "U", "H"]]

That’s a bit more like it! Here, 4 array elements starting from array indices 0, 4, 8 and 12, are each pushed onto the grid array.

Fortunately we only have four rows to do, but if we had much more this would be really inefficient, so here’s an idea:

rolls.each_slice(4) do |slice|
p slice
end
# =>
["F", "U", "S", "D"]
["S", "K", "E", "P"]
["R", "O", "O", "V"]
["I", "Qu", "U", "H"]

The each_slice method splits the rolls array into slices with 4 elements each, and then iterates through each slice and inspects it with p.

However, if we want to assign this to a variable as a 2d array, rather than print it out straight away, we’ll need to do a little more work:

grid = rolls.each_slice(4).to_a
p grid
# => [["F", "U", "S", "D"], ["S", "K", "E", "P"], ["R", "O", "O", "V"], ["I", "Qu", "U", "H"]]

As nothing needs to be done to each slice as it’s iterated over, we remove the block from the each_slice method, and follow it up with the to_a method which converts the series of slices into an array, which has been assigned to grid.

Now to refactor the code into method calledroll_dice which returns grid implicitly.

def roll_dice
dice = [
["R", "I", "F", "O", "B", "X"],
["I", "F", "E", "H", "E", "Y"],
["D", "E", "N", "O", "W", "S"],
["U", "T", "O", "K", "N", "D"],
["H", "M", "S", "R", "A", "O"],
["L", "U", "P", "E", "T", "S"],
["A", "C", "I", "T", "O", "A"],
["Y", "L", "G", "K", "U", "E"],
["Qu", "B", "M", "J", "O", "A"],
["E", "H", "I", "S", "P", "N"],
["V", "E", "T", "I", "G", "N"],
["B", "A", "L", "I", "Y", "T"],
["E", "Z", "A", "V", "N", "D"],
["R", "A", "L", "E", "S", "C"],
["U", "W", "I", "L", "R", "G"],
["P", "A", "C", "E", "M", "D"],
]
rolls = dice.shuffle.map do |die|
die.sample
end

grid = rolls.each_slice(4).to_a
end
new_grid = roll_dice

Looking pretty good, but the intermediate variable of rolls and grid aren’t actually needed any more; the whole thing can be done in one line.

def roll_dice
dice = [
["R", "I", "F", "O", "B", "X"],
....
["P", "A", "C", "E", "M", "D"],
]
dice.shuffle.map{ &:sample }.each_slice(4).to_a
end
new_grid = roll_dice
p new_grid
# => [["F", "U", "S", "D"], ["S", "K", "E", "P"], ["R", "O", "O", "V"], ["I", "Qu", "U", "H"]]

This is the same as before, but rather than assigning to the roll variable or the grid variables all the methods are chained on in one line and the resulting array is returned implicitly. The do end block has been replaced by this: { &:sample }. The ampersand creates a block with the symbol of the sample method.

Displaying the grid

At the moment the grid isn’t very nice to look at, what with it being full of braces and quote marks, and it being on one line and all. It would be much nicer if it could look like this:

+-----+-----+-----+-----+
| F | U | S | D |
+-----+-----+-----+-----+
| S | K | E | P |
+-----+-----+-----+-----+
| R | O | O | V |
+-----+-----+-----+-----+
| I | Qu | U | H |
+-----+-----+-----+-----+

Perhaps we could achieve this with string interpolation?

puts "+-----+-----+-----+-----+"
puts "| #{grid[0][0]} | #{grid[0][1]} | #{grid[0][2]} | #{grid[0][3]} |"
puts "+-----+-----+-----+-----+"
puts "| #{grid[1][0]} | #{grid[1][1]} | #{grid[1][2]} | #{grid[1][3]} |"
puts "+-----+-----+-----+-----+"
puts "| #{grid[2][0]} | #{grid[2][1]} | #{grid[2][2]} | #{grid[2][3]} |"
puts "+-----+-----+-----+-----+"
puts "| #{grid[3][0]} | #{grid[3][1]} | #{grid[3][2]} | #{grid[3][3]} |"
puts "+-----+-----+-----+-----+"

The individual array elements are interpolated into the string longhand with a series of #{grid[0][0]}. This works, but the syntax isn’t sugary enough for my taste. Here’s an alternative:

puts row = '+-----+-----+-----+-----+'

grid.each do |line|
puts "| #{line.join(' | ')} |"
puts row
end
# =>
+-----+-----+-----+-----+
| F | U | S | D |
+-----+-----+-----+-----+
| S | K | E | P |
+-----+-----+-----+-----+
| R | O | O | V |
+-----+-----+-----+-----+
| I | Qu | U | H |
+-----+-----+-----+-----+

The row variable is assigned to the row separation string, and is putsed as it’s assigned. Iterating throughgrid, each line is joined with the join method, delimited by a string with some whitespace for padding and a pipe (|), which is interpolated into another string for the outside pipes as the edges of the grid, and after each line another row is putsed.

It’s almost perfect, but can you spot the problem? Yes, that pesky “Qu” is messing up the spacing.

puts row = '+-----+-----+-----+-----+'

grid.each do |line|
puts "| #{line.map{|c| c.ljust(2)}.join(' | ')} |"
puts row
end
# =>
+-----+-----+-----+-----+
| F | U | S | D |
+-----+-----+-----+-----+
| S | K | E | P |
+-----+-----+-----+-----+
| R | O | O | V |
+-----+-----+-----+-----+
| I | Qu | U | H |
+-----+-----+-----+-----+

Before calling the join method, here map is called on the line, and each character (c) is given trailing whitespace to extend it to 2 characters by the ljust (left justify) method. The delimiting whitespace on join is adjusted to compensate too.

Let’s refactor this into a method called display:

def display(grid)
puts row = '+-----+-----+-----+-----+'
grid.each do |line|
puts "| #{line.map{|c| c.ljust(2)}.join(' | ')} |"
puts row
end
end

Nice.

Getting classy

What if we want to have more than one game on the go? Let’s refactor everything into a class called Boggle:

class Boggle
def initialize
@grid = @@dice.shuffle.map{ &:sample }.each_slice(4).to_a
end

def display
puts row = '+-----+-----+-----+-----+'
@grid.each do |line|
puts "| #{line.map { |c| c.ljust(2)}.join(" | ")} |"
puts row
end
end
@@dice = [
['R', 'I', 'F', 'O', 'B', 'X'],
['I', 'F', 'E', 'H', 'E', 'Y'],
['D', 'E', 'N', 'O', 'W', 'S'],
['U', 'T', 'O', 'K', 'N', 'D'],
['H', 'M', 'S', 'R', 'A', 'O'],
['L', 'U', 'P', 'E', 'T', 'S'],
['A', 'C', 'I', 'T', 'O', 'A'],
['Y', 'L', 'G', 'K', 'U', 'E'],
['Qu', 'B', 'M', 'J', 'O', 'A'],
['E', 'H', 'I', 'S', 'P', 'N'],
['V', 'E', 'T', 'I', 'G', 'N'],
['B', 'A', 'L', 'I', 'Y', 'T'],
['E', 'Z', 'A', 'V', 'N', 'D'],
['R', 'A', 'L', 'E', 'S', 'C'],
['U', 'W', 'I', 'L', 'R', 'G'],
['P', 'A', 'C', 'E', 'M', 'D'],
]
end
new_game = Boggle.new
new_game.display
# =>
+-----+-----+-----+-----+
| F | U | S | D |
+-----+-----+-----+-----+
| S | K | E | P |
+-----+-----+-----+-----+
| R | O | O | V |
+-----+-----+-----+-----+
| I | Qu | U | H |
+-----+-----+-----+-----+

The roll_dice method has been split in two, with the dice variable now @@dice, a class scope variable, so it can be accessed by any instance of the class. The rest of the method is now part of the initialize method, which is run when a new instance of the class is initialised. The display method is the same, just referencing the instance scope@grid variable, meaning the variable in this instance rather than one belonging to a different instance.

Great! Now we can play a game of Boggle even if all we have is Ruby installed, and some paper and pens to write down our words.

Thanks for reading! Check out the next part here.

--

--

Harry Turnbull
Harry Turnbull

Written by Harry Turnbull

Improviser with Gamez Improv, Improbotics, The Nursery Theatre and Hoopla Impro. Learning to program properly with Makers Academy.

No responses yet