Make Your First Text Adventure in Ruby

Meet Ruby

MINASWAN

"Matz is nice and so we are nice."

Ruby is a dynamic, object oriented programming language. It was invented in the mid 1990s by Yukihiro "Matz" Matsumoto.

When you are programming in ruby, you are programming in a language that was designed with your happiness in mind. This is reflected in the languages' beautiful not-quite-prose syntax, its wide range of expression and its expansive standard library.

I suppose it would be fitting to start with the classic "hello world" application.

puts "Hello, world!"

That was simple enough. How about the classic interview question, FizzBuzz?

1.upto(100) do |number|
  line = ""
  line << "Fizz" if number % 3 == 0
  line << "Buzz" if number % 5 == 0
  puts line.empty? ? number : line
end

This one may need a little explaining, which we will get to in good time.

The Usual Suspects

Ruby provides you with several types which allow you to represent and interact with data in a predictable way.

Type Examples
String "I", "love", "ruby", "1", "1.23", "'{};'#"
Symbol :this, :is, :a, :symbol
Fixnum 1, 2, 3, 100, 1_000_000, 999999999999999999
Bignum 9999999999999999999
Float 1.23
TrueClass true
FalseClass false
NilClass nil
Array [], [1, 2, 3], [[], [], []], [123, "foo"]
Hash { "foo" => "bar" }, { :baz => 123 }

These are all basic types, but they contain vast power. Each of these types is backed by a class.

"I love ruby".class
# => String

A class is a collection of information and behaviour. Of attributes and methods. Each of the above types is a class, and each class comes packed with functionality. You can get a preview of this by calling the #instance_methods method of any of these classes.

Fixnum.instance_methods
# => [:to_s, :inspect, :-@, :+, :-, :*, :/, :div, :%,
#     :modulo, :divmod, :fdiv, :**, :abs, :magnitude,
#     :==, :===, :<=>, :>, :>=, :<, :<=, :~, :&, :|,
#     :^, :[], :<<, :>>, :to_f, :size, :bit_length,
#     :zero?, :odd?, :even?, :succ, :integer?, :upto,
#     :downto, :times, :next, :pred, :chr, :ord, :to_i,
#     :to_int, :floor, :ceil, :truncate, :round, :gcd,
#     :lcm, :gcdlcm, :numerator, :denominator, :to_r,
#     :rationalize, :singleton_method_added, :coerce, :i, 
#     :+@, :eql?, :remainder, :real?, :nonzero?, :step,
#     ... and many more!

Try it yourself. I won't make you count, though. The Fixnum class has 130 instance methods available to it. This means that there are 130 different things that you can do with the number 1 in ruby.

Operators

Mathematical

Operator Purpose
+ Addition
- Subtraction
* Multiplication
\ Division
% Modulus
** Power

Comparison

Operator Purpose
== equals
!= not equals
> greater than
< less than
>= greater or equal
<= less or equal
<=> returns 0 if equal, 1 if the first operand is greater, -1 if it is smaller
=== tests equality within a case statement

Control Flow

There are many times that you will wish to control what part of your application executes based on some condition.

if condition
  # do something
end

There are times that you will want to do something if the condition is true, and if it is false, something else entirely.

if condition
  # do something
else
  # do something else entirely
end

There are times that you will want to do something unless a condition is true.

unless condition
  # do something
end

Loops

for number in [1,2,3,4,5] do
  # do something with number
end

This is the basic for loop. It is (almost) functionally equivalant to the each loop.

[1,2,3,4,5].each do |number|
  # do something with number
end

We have other types of loop, too.

while conditional
  # do something while conditional is true
end
until conditional
  # do something until conditional is true
end

Left alone, these loops will continue to execute until their conditions evaluate to false or until their sequence is empty. There are other ways to terminate a loop early, should the need arise.

while conditional
  break # terminate the current loop
  next  # skip to the next iteration of the loop
  redo  # restart the current iteration
end

Methods

As you might expect, Ruby allows you to define your own methods.

def say_hello
  puts "Hello, world!"
end

A method can also take one or more arguments.

def greet(someone)
  puts "Hello, #{someone}!"
end

You can supply default arguments to your methods.

def greet(someone="world")
  puts "Hello, #{someone}!"
end
greet
# Hello, world!

You will commonly find yourself supplying more than one argument to your methods.

def foo(bar, boo, baz)
end

Resist the urge to pass in too many arguments. If you need that many arguments, it is likely that your method is doing too much and your code should be restructured. If you must pass in a larger number of arguments, consider passing in a hash.

As with variable names, it is the convention in Ruby to name your variables in lower case, with words separated by underscores.

Classes

A class is like a blueprint for an object. It defines the data and behaviour that will describe an object.

class Speaker
  def initialize
    @utterance = "Hello, World!"
  end

  def speak
    puts @utterance
  end
end

speaker = Speaker.new
speaker.speak
# Hello, World!

Above, we created an instance, speaker, of our class, Speaker.

Modules

Modules provide a way to include and share behaviour in your classes and objects while still organising your application in a modular way. They are similar to classes, except you cannot create an instance of a module. You can merely include (or "mix in") the functionality into other objects.

module Talkative
  def speak
    puts "Hello, this is #{self.class}!"
  end
end

class Human
  include Talkative
end

class Dog
  include Talkative
end

Human.new.speak
# Hello, this is Human!
Dog.new.speak
# Hello, this is Dog!

You may notice that our classes are named as nouns, and our modules are named as adjectives. This is the generally agreed upon convention as our classes can be instantiated to represent objects. They represent individuals. A human, a dog. Our modules represent behaviour, and when mixed in they represent a quality that can be ascribed to an object.

I have just shown you how to include a module in a class. Now I shall show you that you can also prepend a module to a class.

module Talkative
  def speak
    puts "Hello from the module!"
  end
end

class Human
  include Talkative

  def speak
    puts "Hello from the class!"
  end
end

class Dog
  prepend Talkative

  def speak
    puts "Hello from the class!"
  end
end

Human.new.speak
# Hello from the class!
Dog.new.speak
# Hello from the module!

As you see, the different between include and prepend is that the former places a higher priority to conflicting methods defined within the class. With the latter, methods defined in the module take priority.