Books / Ruby for Beginners / Chapter 15

Methods in Ruby

Methods (or functions) are usually small chunks of code that you can reuse. Until this moment we haven’t reused the code that we had written (expect for code inside of blocks, like in loop do...end). But with methods we can improve and optimize our programs by splitting it into multiple logical blocks.

Methods shouldn’t necessarily make our program shorter. The main idea is to define logical parts and make a program more readable for a human. Sometimes this process is called “refactoring” (there are many refactoring techniques, and this particular technique is called “extract method”). The result this refactoring is logical blocks of code extraction into one or more methods, so this code can be reused later in a program again.

But we can introduce methods for our own convenience. Do you remember we were using the following code to convert user input to a number?

age = gets.to_i

Code above gets user input and converts it from String to Integer (with .to_i) and saves result to age variable. This expression doesn’t look self-explanatory to someone who is looking to the code for the first time. Let’s improve it a bit, so it will (hopefully) become easier to understand:

def get_number
  gets.to_i
end

age = get_number

Code is the same, but on lines 1-3 we defined a method with def...end syntax. This method won’t get executed, until we explicitly call it (on line 5 in our case). Now it looks little bit easier, especially if we want to initialize multiple variables this way:

age = get_number
salary = get_number
rockets = get_number

Methods in Ruby always return value, even if it seems they don’t return any. Result of the last expression is the return value for a method. In our case above we have only one line inside of get_number method (gets.to_i) and since it’s the last line, method get_number will return result of this expression. But if for some reason we want to return value earlier, we can use return keyword:

def check_if_world_is_crazy?
  if 2 + 2 == 4
    return false
  end

  puts "Jesus, I can't believe that"
  true
end

Last line can be written as return true, but it’s up to you (and coding conventions, Rubocop is the right tool to check your style). Method can also accept parameters:

def get_number(what)
  print "Enter #{what}: "
  gets.to_i
end

age = get_number('age')
salary = get_number('salary')
rockets = get_number('number of missiles to launch')

Result:

Enter age: 10
Enter salary: 3000
Enter number of missiles to launch: 5

Would you agree that program above looks much more readable than the following?

print 'Enter age:'
age = gets.to_i
print 'Enter salary:'
salary = gets.to_i
print 'Enter number of missiles to launch:'
rockets = gets.to_i

Ruby Methods Example: Judgement Day Emulator

Let’s practice a bit to sum up everything we know about methods. The machines have taken over the world. The is a struggle for survival. Who will survive: humanity or machines? Now it’s up to destiny to decide our future. Well, actually up to the random number generator.

Program will show the steam of upcoming events happening in the world. It would be much more interesting if we could do it by using some graphics, but in our case it will all depend on observer’s imagination. One may like or program and even use it as screensaver.

Important note: this program can be done the different and better way. But we still have limited Ruby knowledge, so our goal is more to practice, rather than delivering a piece of art.

First, let’s agree that there are only 10000 humans and the same number of machines left. With every iteration we’ll have only one random event. And the number of humans and machines will be decreasing with the same probability. Victory is when there are no more machines (or humans) left. Let’s proceed with a solution.

We’ll start with definition of victory and the main loop with two variables:

humans = 10_000
machines = 10_000

loop do
  if check_victory?
    exit
  end
  ...
end

Variables humans and machines represent the information about survivors.

Method check_victory? returns Boolean type value if one side has won (it doesn’t matter which one). If there is a victory, we just exit the program. If there is no victory, we continue iteration inside of infinite loop. Let’s also print information about who actually won the battle inside of this method.

Now we need to define a couple of events that can happen. Let’s call them event1, event2, and event3. We’ll be calling this or another method depending on random number provided by rand. It’s like throwing dice with only 3 distinct values from 1 to 3:

def event1
  # ...
end

def event2
  # ...
end

def event3
  # ...
end

# ...

dice = rand(1..3)

if dice == 1
  event1
elsif dice == 2
  event2
elsif dice == 3
  event3
end

We used elsif keyword above (we’re already familiar with else). elsif is one of the less intuitive abbreviations in Ruby, and it just means else if.

And we’ll finish our loop with sleep statement, which will delay the execution of our program by certain amount of seconds (from 0.3 to 1.5):

sleep rand(0.3..1.5)

Complete version of the program:

########################################
# DEFINE VARIABLES
########################################

@humans = 10_000
@machines = 10_000

########################################
# AUXILIARY METHODS
########################################

# Method returns random value: true or false
def luck?
  rand(0..1) == 1
end

def boom
  diff = rand(1..5)
  if luck?
    @machines -= diff
    puts "#{diff} machines destroyed"
  else
    @humans -= diff
    puts "#{diff} humans killed"
  end
end

# Method returns random city name
def random_city
  dice = rand(1..5)
  if dice == 1
    'San Francisco'
  elsif dice == 2
    'Moscow'
  elsif dice == 3
    'Beijing'
  elsif dice == 4
    'London'
  else
    'Seoul'
  end
end

def random_sleep
  sleep rand(0.3..1.5)
end

def stats
  puts "#{@humans} humans and #{@machines} machines left"
end

########################################
# EVENTS
########################################

def event1
  puts "Missile launched against #{random_city}"
  random_sleep
  boom
end

def event2
  puts "Nuclear weapon used against #{random_city}"
  random_sleep
  boom
end

def event3
  puts "Group of soldiers have breached defence in #{random_city}"
  random_sleep
  boom
end

########################################
# CHECKING VICTORY
########################################

def check_victory?
  false
end

########################################
# MAIN LOOP
########################################

loop do
  if check_victory?
    exit
  end

  dice = rand(1..3)

  if dice == 1
    event1
  elsif dice == 2
    event2
  elsif dice == 3
    event3
  end

  stats
  random_sleep
end

Sample result:

Nuclear weapon used against London
3 machines destroyed
10000 humans and 9997 machines left
Group of soldiers have breached defence in London
2 machines destroyed
10000 humans and 9995 machines left
Nuclear weapon used against Seoul
4 humans killed
...

Exercise 1 Implement check_victory? method (now it just returns false). In case of victory it should show information about who actually won the battle and who lost. Replace 10_000 with 10 so it will be easier to work on this program (you won’t need to wait too long to see how your method works).

Exercise 2 Lookup documentation for “ruby case statements” and replace if...else with case...when.

Exercise 3 Improve the program, so every iteration we not only lose, but also get random number of humans and machines. Theoretically, this battle may never end, but with low initial number of humans and machines at the beginning (like 10 or 100 instead of 10000) there can be a case when one side randomly wins.

Exercise 4 Improve the program and add at least 3 events, so your output looks more interesting. Use your imagination.


Licenses and Attributions


Speak Your Mind

-->