Boggle in Ruby: Getting Words
Previously in this project, I had set up a class that can initialise a game of Boggle, and display the grid of letters to the player.
The project is available on GitHub, so why not check it out and have a game?
Today’s task
So, we’ve displayed the grid to players, now the players will start to write down their list of words that they can see in the grid.
Entering words
Let’s write a little bit of code so players can type in their words:
words = []
puts "Type in your words, press enter to submit."
loop do
input = gets.chomp
words.push(input)
end
First we create the words
array , then display a message to the player telling them to type in their words. The loop
starts, and input is assigned with the gets
method that allows players to type in their word and submit it with enter. The chomp
method removes the line break that is added when the player presses enter. The input
is then added on to the end of the words
array with the push
method. Then loop starts again, allowing another word.
This loop will never stop though, and that’s not good! Let’s add some way to break out of the loop. The player is typing in words, so a command to “quit” won’t be suitable, what if the player wants to add the word “quit”. We’ll use a “!” instead.
loop do
input = gets.chomp
if input == '!'
break
else
words.push(input)
end
endputs "Your words are:"
puts words.join(', ')# =>
Your words are:
here, are, some, words
Now, some if else
logic will check if input
is “!”break
is used to exit the loop. If the input
is not “!”, the if
block is skipped and the else
block is executed instead, adding words to the array as before.
After the loop is broken out of, a message and the list of words the player has entered (separated with commas) will be puts
d.
Counting down
Now this is working the player can enter words they find to their hearts content, and when they’re done they can stop, but in Boggle players only have three minutes to enter words. We need a timer:
time = 180
puts time
while time > 0
sleep 1
time -= 1
puts time
end
Here’s a basic timer that counts down 180 seconds. First time
is assigned to 180 and time its puts
d, then a while
loop starts, the sleep
method causes the program to wait for 1 second before executing the next line, which decrements time
by 1, then puts
it. The loop only repeats as long as time
more than 0.
Now, there’s a little bit of a problem, at the moment if we run the timer, Ruby will be busy running the timer, and it won’t be able to take words from the player. To do both at the same time we need to use threads. Threads allow Ruby to do work on more than one thing at once:
player_input = Thread.new do time = 180 timer = Thread.new do
while time > 0
sleep 1
time -= 1
end
puts "\nTime's up!"
player_input.exit
end
loop do
input = gets.chomp
if input == '!'
timer.exit
puts "You quit early with #{time} seconds to go."
break
else
words.push(input)
end
end
endplayer_input.joinputs 'Your words are:'
puts words.join(', ')# player quit early =>
You quit early with 30 seconds to go.
Your words are:
here, are, some, words# player ran out of time =>
Time's up!
Your words are:
here, are, some, words
Whew! Here’s a timer integrated in. It’s a little complicated so let’s break down what’s happening.
The player_input
thread is started and time
is set to 180. Then the timer
thread is started, nested within the player_input
thread, counting down from 180. Once the loop is finished, an escaped new line (\n
) and “Time’s up!” are puts
d, and the player_input
thread is exited with the exit
method, the nested timer
thread too. This is useful as even though the player will be in a gets
input, it will break them out of that, even mid-word.
While the timer is ticking down going on, the loop collects words from the player, and if the player types “!” to quit early, the timer
thread is stopped with the exit
method, and the player gets a message telling them how much time they had left using string interpolation. (note that in this case, timer
thread never got to finish so the “Time’s up!” message isn’t sent).
The player_input.join
method causes the main thread to wait until the player_input
thread finishes before continuing to execute, otherwise the main thread would head on to display the list of words and end without waiting for the timer to run down.
Finally, the list of words is displayed.
Staying Classy
It’s refactor time! Let’s get this code integrated with the Boggle
class:
class Boggle
def initialize
@grid = @@dice.shuffle.map(&:sample).each_slice(4).to_a
@words = []
@time = 180
enddef display
puts row = '+-----+-----+-----+-----+'
@grid.each do |line|
puts "| #{line.map { |c| c.ljust(2) }.join(' | ')} |"
puts row
end
enddef play
puts 'Type in your words, press enter to submit.'
player_input = Thread.new do
timer = Thread.new do
while @time > 0
sleep 1
@time -= 1
end
puts "\nTime's up!"
player_input.exit
end
loop do
input = gets.chomp
if input == '!'
timer.exit
puts "You quit early with #{@time} seconds to go."
break
else
@words.push(input)
end
end
end
player_input.join
puts 'Your words are:'
puts @words.join(', ')
end@@dice = [
%w[R I F O B X],
%w[I F E H E Y],
%w[D E N O W S],
%w[U T O K N D],
%w[H M S R A O],
%w[L U P E T S],
%w[A C I T O A],
%w[Y L G K U E],
%w[Qu B M J O A],
%w[E H I S P N],
%w[V E T I G N],
%w[B A L I Y T],
%w[E Z A V N D],
%w[R A L E S C],
%w[U W I L R G],
%w[P A C E M D]
]
endnew_game = Boggle.new
new_game.display
new_game.play# =>
+-----+-----+-----+-----+
| F | U | S | D |
+-----+-----+-----+-----+
| S | K | E | P |
+-----+-----+-----+-----+
| R | O | O | V |
+-----+-----+-----+-----+
| I | Qu | U | H |
+-----+-----+-----+-----+
Type in your words, press enter to submit.
fuse
fused
deus
rook
hope
hopes
Time's up!
Your words are:
fuse, fused, deus, rook, hoop, hopes
There we are, all nicely integrated as a method. Now we don’t even need paper and pens, or a stopwatch to play Boggle! Sweet!
Thanks for reading, see you soon!