Simple RPG - Alice fights Bob
When one character is dead, the game is over.


No, it isn't, because in your own example output it says:


  Alice attacks Bob [...] Bob has been defeated
  
  Bob attacks Alice [...] Alice has been defeated
  
  The game is over!


Bob is apparently dangerous, even in death.

Anyway, code review:

I'd prefer that the characters weren't responsible for printing anything. Their responsibility should only be to be number generators; let the game handle the encounter.

I'd also advise against using attr_accessor :hit_points, since that means you can set the hit points from anywhere. Using attr_reader would be more fitting, I think.

Lastly, naming you instance variables @alice and @bob is rather too specific; they're both just characters. Besides, you might want more at some point.

Here's how I might do the character class:

class Character
  attr_reader :name, :hit_points, :damage

  def initialize(name, hit_points, damage, hit_chance, block_chance)
    @name = name
    @hit_points = hit_points
    @damage = damage
    @hit_chance = hit_chance
    @block_chance = block_chance
  end

  def dead?
    @hit_points &lt;= 0
  end

  def hits?
    roll(@hit_chance)
  end

  def blocks?
    roll(@block_chance)
  end

  def take_damage(amount)
    @hit_points -= amount
  end

  def block_damage
    @block_damage ||= (damage / 2).round
  end

  private

  def roll(threshold)
    rand(100) &gt; threshold
  end
end


As you can see, it just a data model; it doesn't manipulate anything outside itself.

For the game class, here's a simple implementation that has 3 characters in the mix (everyone gets an attack at each of the others, which isn't too RPG'ish, but it's just for illustration).

class Game
  def initialize
    @characters = []
    @characters &lt;&lt; Character.new("Alice", 5, 2, 75, 50)
    @characters &lt;&lt; Character.new("Bob", 5, 2, 75, 50)
    @characters &lt;&lt; Character.new("Carol", 5, 2, 75, 50)
  end

  def play
    while play_turn
      puts "----------------------------"
      print_status
      puts "----------------------------"
      sleep 1
    end
  end

  def play_turn
    living_characters.permutation(2) do |a, b|
      puts "#{a.name} attacks #{b.name}"

      if a.hits?
        blocked = b.blocks?
        damage = blocked ? a.block_damage : a.damage
        puts "\t#{b.name} blocks" if blocked
        puts "\t#{a.name} inflicts #{damage} points of damage"
        b.take_damage(damage)
        puts "\t#{b.name} has been defeated" if b.dead?
      else
        puts "\t#{a.name} misses"
      end

      if living_characters.size == 1
        puts "Game over, #{a.name} wins"
        return false
      end
    end
    true
  end

  def print_status
    living_characters.each do |player|
      puts "#{player.name}: #{player.hit_points} hit points"
    end
  end

  private

  def living_characters
    @characters.reject(&amp;:dead?)
  end
end


The game class is responsible for printing too, which isn't necessary the nicest way to do things. It conflates the view and controller, even though the model (the character) is now nicely encapsulated. However, I don't know the exact ruleset and UI you're going for, so I'll leave it as-is.

This class also does things a little differently than yours: The game ends the moment there's only one character left alive; recently dead characters do not get a final swing.