Books / Ruby for Beginners / Chapter 19

Code Example - Battle of Robots

Let’s build a simple game together to sum up all information from previous chapters. We’ll have 20 robots and two teams, 10 robots in each. Every team will be represented by its own array with the size of 10. Element of array (cell) may have only one of two values:

  • Zero, 0 - when robot is destroyed
  • One, 1 - when robot is still alive

Define two arrays. 1 in example below indicates that we define array with robots who are [still] alive:

arr1 = Array.new(10, 1)
arr2 = Array.new(10, 1)

Each team will attack after another. But what does it mean “to attack” in our case? If 1 in array is alive robot, and 0 is dead robot, then “to attack” means “changing the value for certain cell in array from 1 to 0”. But which cell are we going to attack? We have ten of them for each team. We have two options:

  • “Attack” cells one by one sequentially. In other words, at the beginning we attack cell 1 of array 1, then cell 1 of array 2, and so on. Whoever starts attacking first wins the game. Predictable and doesn’t sound too interesting.
  • It’s much more fun to pick index randomly. Randomness isn’t a guarantee that index will remain unique while generating random numbers, so one team can attack the same cell of another team. For example, on a fifth turn second team attacks third cell, but it’s possible that this cell was attacked before. So team won’t reach its goal in this turn of destroying enemy, because cell already equals to zero. And the number of destroyed enemies will remain the same. With this approach result of the battle is always a matter of luck.

So let’s stick with the latter approach and implement our game this way. We already know how to generate random index from 0 to 9:

i = rand(0..9)

We only need to access the element of array, and replace it with zero if it equals one. And we can find out if cell has been already attacked if its value is zero. Here is the Ruby code that implements this logic:

if arr[i] == 1
  arr[i] = 0
  puts "Robot by index #{i} destroyed"
else
  puts 'Oops, missed that!'
end

Team wins if all robots of another team have been destroyed. It would be useful to know how many alive robots do we have in array. Imagine we have the following array:

arr = [1, 0, 1, 0, 1, 1]

How do we get the number of elements equal to 1? Take a break and think about it. We already know at least one approach.

The answer is to use each method and increase variable while iterating over array:

arr = [1, 0, 1, 0, 1, 1]
x = 0
arr.each do |element|
  if element == 1
    x += 1
  end
end
puts "Array has #{x} ones"

Program above works, but there is also elegant way of doing that. Method count of Array class does exactly what we want, but the syntax is easier (please look up documentation before you read example below):

arr = [1, 0, 1, 0, 1, 1]
x = arr.count do |x|
  x == 1
end
puts "Array has #{x} ones"

Or the same code with keeping block on one line:

arr = [1, 0, 1, 0, 1, 1]
x = arr.count { |x| x == 1 }
puts "Array has #{x} ones"

Now we have all we need. Two teams of robots, each team will attack after another. Team wins if all robots of other team has been destroyed. We can implement this game, and important note here is that we don’t need any user input, it runs automatically, we’ll be just observing.

Exercise Try to implement this game by yourself. There is a chance that something can go wrong, but remember that you’re here for practice.

Here is how author would implement this game:

###############################
# DEFINE ARRAYS
###############################

# array for the first team
@arr1 = Array.new(10, 1)
# array for the second team
@arr2 = Array.new(10, 1)

###############################
# ATTACK
###############################

# Method accepts array for attack
def attack(arr)
  sleep 1 # Add sleep here, so our program won't run too fast
  i = rand(0..9)
  if arr[i] == 1
    arr[i] = 0
    puts "Robot by index #{i} has been destroyed"
  else
    puts "Missed at index #{i}"
  end
  sleep 1 # one more sleep to beautify the output
end

###############################
# VICTORY CHECK
###############################

def victory?
  robots_left1 = @arr1.count { |x| x == 1 }
  robots_left2 = @arr2.count { |x| x == 1 }

  if robots_left1 == 0
    puts "Team 2 wins, #{robots_left2} robots left"
    return true
  end

  if robots_left2 == 0
    puts "Team 1 wins #{robots_left1} robots left"
    return false
  end

  false
end

###############################
# STATS
###############################

def stats
  # number of alive robots for the first and second team
  cnt1 = @arr1.count { |x| x == 1 }
  cnt2 = @arr2.count { |x| x == 1 }
  puts "1st team has #{cnt1} robots left"
  puts "2nd team has #{cnt2} robots left"
end

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

loop do
  puts 'First team is going to attack...'
  attack(@arr2)
  exit if victory?
  stats
  sleep 3
  puts # empty line

  puts 'Second team is going to attack...'
  attack(@arr1)
  exit if victory?
  stats
  sleep 3
  puts # empty line
end

Result:

First team is going to attack...
Robot by index 9 has been destroyed
1st team has 10 robots left
2nd team has 9 robots left

Second team is going to attack...
Robot by index 0 has been destroyed
1st team has 9 robots left
2nd team has 9 robots left

...(skip)...


Exercise 1 Add more details to stats method so it prints the state of two arrays.

Exercise 2 Improve program the following way. Each cell in array must represent the percentage of life of a robot from 1 to 100. Initial value should be 100. Each attack should take random number from robot’s life value. This random number should be from 30 to 100. If life number is zero or less, we should consider this robot destroyed.


Licenses and Attributions


Speak Your Mind

-->