Snakes and ladders random world builder
I'm going to be bold and review my own code having spent the last hour refactoring it.

The original implementation had bugs. Sometimes less than 10 ladders were placed. This is because the algorithm did not check to see if there was a ladder already present at the current insertion point.

# Tough luck if start_point is 10 and there's already a ladder there
begin
  start_point = rand(ladder_habitable_range)
end while snakes.keys.include?(start_point)


The best way to ensure a bullet proof implementation is to avoid having a square contain more than one object. For example, there's an edge case where a square can contain a ladder's head and a tail, causing you to progress even further.

To do that, the placement should be delegated to a method that checks if the square is occupied. This requires tweaking the initialize method, but it's worth it:

  def place_point(range = habitable_range)
    begin
      point = rand(range)
    end while occupied?(point)
  end

  def occupied?(number)
    [snakes.keys, snakes.values, ladders.keys, ladders.values].flatten.include?(number)
  end


Doing so means the placement methods for ladders and snakes only have to worry about working out the range and populating their respective hashes, which are the only two differences between them. This reduces duplication.

  def place_snakes(count)
    count.times do
      start_point = place_point
      end_point_range = habitable_range.begin..(start_point - 1)

      snakes[start_point] = place_point(end_point_range)
    end
  end


Finally The habitable range for ladders and snakes is the same, so this can be replaced with a single range 1..(size - 1):

def snake_habitable_range
  2..(size - 1)
end

def ladder_habitable_range
  1..(size - 1)
end


Behold! My new implementation.

class World
  attr_reader :size, :snakes, :ladders

  def initialize(size, snakes = 10, ladders = 10)
    @size = size
    @snakes, @ladders = {}, {}
    place_snakes(snakes)
    place_ladders(ladders)
  end

  def place_snakes(count)
    count.times do
      start_point = place_point
      end_point_range = habitable_range.begin..(start_point - 1)

      snakes[start_point] = place_point(end_point_range)
    end
  end

  def place_ladders(count)
    count.times do
      start_point = place_point
      end_point_range = (start_point + 1)..habitable_range.end

      ladders[start_point] = place_point(end_point_range)
    end
  end

private

  def place_point(range = habitable_range)
    begin
      point = rand(range)
    end while occupied?(point)
  end

  def occupied?(number)
    [snakes.keys, snakes.values, ladders.keys, ladders.values].flatten.include?(number)
  end

  def habitable_range
    1..(size - 1)
  end
end