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__