Matthew Moss ha scritto:
> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
>
> The three rules of Ruby Quiz 2:
>
> 1.  Please do not post any solutions or spoiler discussion for this
> quiz until 48 hours have passed from the time on this message.
>
> 2.  Support Ruby Quiz 2 by submitting ideas as often as you can! (A
> permanent, new website is in the works for Ruby Quiz 2. Until then,
> please visit the temporary website at
>
>      <http://splatbang.com/rubyquiz/>.
> 3.  Enjoy!
>
> Suggestion:  A [QUIZ] in the subject of emails about the problem
> helps everyone on Ruby Talk follow the discussion.  Please reply to
> the original quiz message, if you can.
> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
>
> ## Circle Drawing (#166)
>
> This week we're going to keep it simple... very simple.
>
> Given a radius, draw an ASCII circle.
>
> For example:
>
>     ruby circle.rb 7
>
> Should produce a circle of radius 7:
>
>          #####
>        ##     ##
>       #         #
>      #           #
>      #           #
>     #             #
>     #             #
>     #             #
>     #             #
>     #             #
>      #           #
>      #           #
>       #         #
>        ##     ##
>          #####
>
>
> Note that most fonts do not have a square aspect ratio, which is why the
> above output may look like an oval, despite my calculations for a circle. It
> is acceptable if your code produces similar output.
>
>
> However, _for extra credit_ you may support an additional argument that
> specifies the aspect ratio (height divided by width).
>
>     ruby circle.rb 7 1.4
>
> This should draw a circle of radius 7 with aspect ratio of 1.4. If done
> correctly, your output will actually look like a circle (assuming 1.4 is an
> accurate measure of the actual aspect ratio).
>
>
>
>   
Here my solution. It is available on pastie:

http://pastie.org/215379
http://pastie.org/215380 (specs)

and it is also attached below:

#
# Solution to Ruby Quiz #166 - Circle Drawing
#
# Usage:
#
# Circle.new(5).to_s
#
# or:
#
# Circle.new(5, 2).to_s
#
#
# or:
#
# Circle.new(5, 2, 'x').to_s
#

# Objects of class Circle draw circles on stdout. The aspect ratio
# correction is actually made drawing an ellipse with semimajor axis
# (a) equals to the given circle radius and semiminor axis (b) equals
# to a / aspect_ratio.
#
# Circle class is responsible to
#
#  * initialize a Circle object with the given radius, aspect ratio
#    and drawing char
#  
#  * initialize a canvas
#
#  * draw the circle on its internal canvas
#
#  * convert the canvas to string for output on stdout
#
class Circle

  # cx, cy are the coordinates of the circle's center.
  attr_reader :cx, :cy

  attr_reader :radius
 
  # w, h are width and height of the canvas
  attr_reader :w, :h
 
  # canvas is a linear array that is initially filled with spaces
  attr_reader :canvas
 
  #
  # Initialize a Circle object passing a value for radius, aspect
  # ratio and drawing character.
  #
  def initialize(radius, aspect_ratio = 1.0, char = '#')
    
    @radius = radius.to_i
    @aspect_ratio = aspect_ratio.to_f
    @char = char

    fail "Error: radius must be > 0" if @radius <= 0
    fail "Error: aspect ratio must be > 0" if @aspect_ratio <= 0
        
    # a is the semimajor axis of the ellipse and is equal to the given
    # radius
    @a = @radius
    
    # b is the semiminor axis of the ellipse and is calculated from a
    # and the given aspect ratio
    @b = (@a / @aspect_ratio).ceil
    
    # calculate the size of the canvas
    @w, @h = (@a + 1) * 2, (@b + 1) * 2
    
    # center coordinates correspond to the size of semiaxis.
    @cx, @cy = @a, @b
    
    # initialize the canvas with spaces
    @canvas = Array.new(@w * @h, ' ')
    
    # draw ellipse on canvas
    draw_ellipse(@a, @b)
  end
 
  #
  # Print circle on stdout.
  #
  def to_s
    result = ""
    (0..@h - 1).each do |line|
      result << @canvas[line * @w..line * @w + @w - 1].to_s << "\n"
    end
    result
  end
 
  private

  #
  # Draw the given character on canvas to the given coordinates.
  #
  def point(x, y)
    @canvas[y * @w + x] = @char
  end
 
  #
  # Translates and mirrors point (x, y) in the quadrants taking
  # advantage of the simmetries in the ellipse. Thus, for a given
  # point (x, y) the method plot three other points in the remaining
  # quadrants.
  #
  def plot_four_points(x, y)
    point(@cx + x, @cy + y)
    point(@cx - x, @cy + y)
    point(@cx + x, @cy - y)
    point(@cx - x, @cy - y)
  end
 
  #
  # Draw an ellipse on canvas. This method implements a Bresenham
  # based algorithm by John Kennedy
  # (http://homepage.smc.edu/kennedy_john/BELIPSE.PDF)
  #
  # The method calculates two set of points in the first quadrant. The
  # first set starts on the positive x axis and wraps in a
  # counterclockwise direction until the tangent line slope is equal
  # to -1.  The second set starts on the positive y axis and wraps in
  # a clockwise direction until the tangent line slope is equal to -1.
  #
  def draw_ellipse(a, b)
    a_square = 2 * a**2
    b_square = 2 * b**2
    
    draw_first_set(a, b, a_square, b_square)
    draw_second_set(a, b, a_square, b_square)
  end
 
  #
  # The method increments y and decides when to decrement x testing
  # the sign of a function.  In this case, the decision function is
  # (2*ellipse_error+x_change) and its value is calculated
  # iteratively.
  #
  def draw_first_set(a, b, a_square, b_square)
    
    x, y = a, 0
    x_change, y_change = b**2 * (1 - 2 * a), a**2
    stopping_x, stopping_y = b_square * a, 0
    ellipse_error = 0
    
    while(stopping_x >= stopping_y) do
      plot_four_points(x, y)
      y += 1
      stopping_y += a_square
      ellipse_error += y_change
      y_change += a_square
      if (2 * ellipse_error + x_change) > 0
        x -= 1
        stopping_x -= b_square
        ellipse_error += x_change
        x_change += b_square
      end
    end      
    
  end

  #
  # The method increments x and decides when to decrement y testing
  # the sign of a function.  In this case, the decision function is
  # (2*ellipse_error+y_change) and its value is calculated
  # iteratively.
  #
  def draw_second_set(a, b, a_square, b_square)
    
    x, y = 0, b
    x_change, y_change = b**2, a**2 * (1 - 2 * b)
    stopping_x, stopping_y = 0, a_square * b
    ellipse_error = 0
    
    while stopping_x <= stopping_y do
      plot_four_points(x, y)
      x += 1
      stopping_x += b_square
      ellipse_error += x_change
      x_change += b_square
      if (2 * ellipse_error + y_change) > 0
        y -= 1
        stopping_y -= a_square
        ellipse_error += y_change
        y_change += a_square
      end
    end  
    
  end
 
end

# Usage:
#
# ruby circle.rb 7 #=> print out a circle of radius 7
#
# ruby circle.rb 7 1.8 #=> print out a circle of radius 7 and aspect 
ratio 1.8
#
# ruby circle.rb 7 1.8 x #=> print out a circle of radius 7 and aspect 
ratio 1.8
#                            using the ascii char 'x'
#

print Circle.new(ARGV[0], ARGV[1] || 1.0, ARGV[2] || '#').to_s if $0 == 
__FILE__