Speed typing test game in Ruby

Konstanty Koszewski
3 min readMay 12, 2024

Game which is about to measure your speed of typing on a keyboard.

List of the words can be found here.

Gemfile:

source 'https://rubygems.org'

gem 'ruby2d'

typing_speeder.rb:

require 'ruby2d'
words_list = IO.readlines('./words.txt').map(&:strip)

WIDTH = 600
HEIGHT = 480

set width: WIDTH
set height: HEIGHT
set fps_cap: 30
set background: 'gray'
set title: 'typing speeder'

def draw_bottom_panel(current_word, seconds, lives, words_typed)
wpm = seconds.zero? ? 0 : (words_typed / (seconds / 60.0)).truncate(2)

Rectangle.new(x: 0, y: HEIGHT - 100, width: WIDTH, height: 100, color: 'blue')
Line.new(x1: 200, x2: 200, y1: HEIGHT - 100, y2: HEIGHT, color: 'white')
Text.new('Press ESC to pause', x: 35, y: HEIGHT - 35, color: 'white', size: 15)

# left top side panel
Text.new("time: #{seconds}", x: 20, y: HEIGHT - 90, color: 'white', size: 15)
Text.new("lives: #{lives}", x: 120, y: HEIGHT - 90, color: 'white', size: 15)
# left bottom side panel
Text.new("words: #{words_typed}", x: 20, y: HEIGHT - 65, color: 'white', size: 15)
Text.new("wpm: #{wpm}", x: 120, y: HEIGHT - 65, color: 'white', size: 15)

# right side panel
Text.new(current_word, x: (WIDTH - 200) / 2 + 20, y: HEIGHT - 90, color: 'white', size: 50)
end

def draw_break_view(text)
Text.new(text, x: WIDTH / 3 - 10, y: HEIGHT / 3, size: 45, color: 'red')
end

def generate_new_word(current_words, words_list)
return Word.new(words_list.sample, WIDTH - rand(50..170), rand(0..HEIGHT - 120)) if current_words.empty?

occupied_pos = current_words.map { |word| [word.x, word.y] }
x = 0
y = 0

# avoid collision between words
loop do
x = WIDTH - rand(50..150)
y = rand(0..HEIGHT - 120)
collision = false

occupied_pos.each do |pos|
collision = true if (pos[0] - 40..pos[0] + 40).include?(x) && (pos[1] - 25..pos[1] + 25).include?(y)
end

break unless collision
end

Word.new(words_list.sample, x, y)
end

class Word
attr_reader :x, :y, :content

def initialize(content, x, y)
@content = content
@x = x
@y = y
end

def draw
Text.new(@content, x: @x, y: @y, size: 20, color: 'blue')
end

def move
@x -= 5
end
end

paused = false
current_word = ''
current_words = []
words_typed = 0
lives = 5
time_start = Time.new
seconds = 0
seconds_before_pause = 0

# generate couple words to start with
5.times do
current_words << generate_new_word(current_words, words_list)
end

update do
clear

draw_bottom_panel(current_word, seconds, lives, words_typed)

if lives <= 0
draw_break_view('GAME OVER')
next
end

if paused
draw_break_view('PAUSED')
seconds_before_pause = seconds
time_start = Time.new
next
end

seconds = seconds_before_pause + (Time.new - time_start).to_i

current_words.each(&:draw)

# each 1/2 of second move each words to the left
current_words.each(&:move) if (Window.frames % 15).zero?

# check if any word reached far left edge of the screen
current_words.each_with_index do |word, index|
if word.x <= 0
lives -= 1
current_words.delete_at(index)
end
end

# each 55 frames (~1.8s) generate a new word
current_words << generate_new_word(current_words, words_list) if (Window.frames % 55).zero?
end

on :key_down do |event|
paused = !paused if event.key == 'escape'
next if paused

current_word += event.key if ('a'..'z').include?(event.key) && current_word.size <= 15

current_word = current_word.chop if event.key == 'backspace'

if event.key == 'return'
# iterate over list of the words present on the screen to see if one of them fits your input
current_words.each_with_index do |word, index|
if word.content == current_word
current_words.delete_at(index)
words_typed += 1
end
end
current_word = ''
end
end

show

--

--