[QUIZ] Circle Drawing (#166)

Matthew Moss wrote:

However, _for extra credit_ ...

What about bonus points for using only *integer* arithmetic and no transcendentals?
I wrote C code for that which is hiding somewhere :slight_smile:

Hi,

For example:

    ruby circle.rb 7

Should produce a circle of radius 7

I'm not sure if this is intentional but the circle is 15 characters
high. Of course, the line has to be counted in too.
Nevertheless ... :slight_smile:

Regards,
Thomas.

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/&gt;\.
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__

Here's my solution, it creates a buffer to draw into, once done it puts it on
the screen:

----- circle.rb -----
class Circle
  def initialize(rad, asp)
    @rad, @asp = rad, asp # radius, horizontal aspect ratio
    @height = rad*2+1 # hight/width of the pictures
    @width = (@height*asp).round
    @buf = Array.new(@height, ' ').map { |e| Array.new(@width, ' ') }
  end
  def draw
     (0..Math::PI*2).step(1/(@rad*@asp*2)) do |deg|
         @buf[((@height/2) + (Math::sin(deg)*@rad)).round] \
             [((@width/2) + (Math::cos(deg)*@rad*@asp)).round] = '#'
     end
     @buf.map { |l| l.join + "\n"}.join
  end
end
puts Circle.new((ARGV[0] || 7).to_i, (ARGV[1] || 1).to_f).draw
----- end circle.rb -----

···

On Friday 13 June 2008 14:47:38 Matthew Moss wrote:

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

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/&gt;\.
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).

My solution makes circles with r=7 14 characters wide. This may be
incorrect. It's rather simple though.

Regards,
Thomas.

def draw_circle(r, ratio=1.0)
    lines = []
    a = 0.0
    t2 = ratio / 2.0
    (t2 - 0.1).step(r, ratio) do |h|
        b = Math.sqrt(2.0 * h * r - h ** 2).round
        u = r - b
        v = [1.0, b - a].max
        w = (r - u - v) * 2.0
        lines << [(m = ' ' * u), (l = '#' * v), ' ' * w, l, m].join
        a = b
    end
    out = lines.join("\n")
    puts out
    puts out.reverse
end

if __FILE__ == $0
    draw_circle(*ARGV.map {|e| e.to_f})
end

My solution follows....

Regards,

Bill

# Ruby Quiz #166

···

#
# This draws a circle of the specified radius, modified
# by an optional aspect ratio and thickness factor.
#
# implementation notes:
# - for the fun of it, i forbade use of sqrt() and trancendentals
# - the circle is not drawn into a buffer before being printed
# - some values are empirically derived (thickness factor, in parciular)
#
# bugs:
# - the thickness factor causes bloat in small circles

ARGV.length >= 1 or abort("usage: #$0 radius [aspect] [thickness]")

radius = ARGV.shift.to_f
radius > 0 or abort("please provide radius of circle")

aspect = ARGV.shift.to_f
aspect > 0 or aspect = 1.0

thick = ARGV.shift.to_f
thick > 0 or thick = 1.0

hradius = (radius * aspect).ceil + (thick/2.0).round
vradius = radius.ceil + (thick/2.0).round

def get_radius_ch(rsq, dsq, tfactor)
  (rsq - dsq).abs <= tfactor ? "*" : " "
end

tfactor = (thick * 4.0) + 2.5
rsq = radius**2
(-vradius).upto(vradius) do |y|
  (-hradius).upto(hradius) do |x|
    print(get_radius_ch(rsq, (x * (1.0/aspect))**2 + y**2, tfactor))
  end
  puts
end

# example: radius 7.0, aspect 1.0, thickness 1.0
#
# $ ruby 166_circle.rb 7 1 1
# # *****
# ** **
# * *
# * *
# * *
# * *
# * *
# ** **
# *****
#
# example: radius 10.0, aspect 2.0, thickness 5.0
#
# $ ruby 166_circle.rb 10 2 5
# # # *****
# *******************
# *************************
# ******** ********
# ******* *******
# ****** ******
# ***** *****
# ***** *****
# ***** *****
# **** ****
# ***** *****
# **** ****
# ***** *****
# ***** *****
# ***** *****
# ****** ******
# ******* *******
# ******** ********
# *************************
# *******************
# *****
#

# circle.rb

def draw(mtrx)
  mtrx.each do |file|
    puts file.to_s
  end
  nil
end

def new_mtrx(rdx,ratio)
  size = rdx * (ratio.ceil) * 2
  mtrx = Array.new(size).map!{ Array.new(size) }
  (0..size-1).each do |file|
    (0..size-1).each do |col|
      mtrx[file][col] = " "
    end
  end
  mtrx
end

def load_mtrx(mtrx,rdx,ratio=1)
  (1..360).each do |grado|
    x = ((Math.sin grado) * (rdx * ratio)) + (rdx * ratio)
    y = ((Math.cos grado) * rdx) + rdx
   mtrx[x.to_i][y.to_i] = "#"
   end
   nil
end

def main
  rdx = ARGV[0].to_i
  ratio = ARGV[1].to_f
  mtrx = new_mtrx(rdx,ratio)
  load_mtrx(mtrx,rdx,ratio)
  draw(mtrx)
end

main

# end file

···

--
«Quien nunca ha cometido un error nunca ha probado algo nuevo.»

Here's mine before I delved into trying to add the aspect ratio feature.

class Circle
  def initialize(radius)
    @radius = radius.to_i
  end
   def draw
    (0..@radius*2).each do |x|
      (0..@radius*2).each do |y|
        print distance_from_center(x,y).round == @radius ? '#' : '.'
      end
      puts
    end
  end
   def distance_from_center(x,y)
    a = calc_side(x)
    b = calc_side(y)
    return Math.sqrt(a**2 + b**2)
  end
   def calc_side(z)
    z < @radius ? (@radius - z) : (z - @radius)
  end
end

Circle.new(ARGV.shift).draw

···

--

http://www.5valleys.com/

http://www.workingwithrails.com/person/8078

Don't reinvent the wheel (no pun intended :slight_smile:

require 'cairo'
radius = ARGV[0].to_f
aspect = (ARGV[1]||1.0).to_f
linewidth = (ARGV[2]||1.0).to_f
width = (aspect * radius * 2 + 1 + linewidth + 0.5).to_i
height = (radius * 2 + 1 + linewidth + 0.5).to_i
Cairo::ImageSurface.new(width, height) do |surface|
  cr = Cairo::Context.new(surface)
  cr.set_antialias(Cairo::ANTIALIAS_NONE)
  cr.set_source_rgb(0,0,0)
  cr.paint
  cr.save
  cr.scale(aspect, 1.0)
  cr.arc(width / 2.0 / aspect + 0.5, height / 2.0 + 0.5, radius, 0, 2
* Math::PI)
  cr.restore
  cr.set_source_rgb(1,1,1)
  cr.set_line_width(linewidth)
  cr.stroke
  height.times { |row|
    puts cr.target.data[row * width * 4, width * 4].unpack("N*").map
{ |x| (x >> 8) > 0 ? '#' : ' ' }.join
  }
end

Matthew Moss ha scritto:

## 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).

A bit of ruby art :slight_smile:

http://pastie.org/216459
http://pastie.org/216462

I don't know if my benchmark is done properly.. I do it quickly and just for fun :slight_smile: Please give me feedback..

Benchmark details:

num_of_runs = 100
radius = 7
ratio = 2.0

This benchmark has been executed on a MacBook 2,16Ghz with 2Gb of RAM running Linux Ubuntu 7.10.

Andrea

Just make test pass.... nothing clever!

Jean Lazarou

---- code -----------------

def symbolify value
  
  res = value.to_s

  def res.delete other_str
    ""
  end

  res
  
end

1000.times do |i|
  s = symbolify(i)
  raise "Not a string!" unless s.is_a? String
  raise "Invalid chars!" unless s.delete("?*()-").empty?

  x = eval(s)
  raise "Decode failed!" unless i == x
end

2*7 = 15, simple LOL.

I finally decided against it because of simplicity, but I believe that
it is more beautyful to add a "middle" line, especially for small r's.

Robert

···

On Sat, Jun 14, 2008 at 8:09 AM, ThoML <micathom@gmail.com> wrote:

Hi,

For example:

    ruby circle.rb 7

Should produce a circle of radius 7

I'm not sure if this is intentional but the circle is 15 characters
high. Of course, the line has to be counted in too.
Nevertheless ... :slight_smile:

Regards,
Thomas.

--
http://ruby-smalltalk.blogspot.com/

---
As simple as possible, but not simpler.
Albert Einstein

Depends on where you are measuring the radius:

outside: 7.5
inside: 6.5 (white space is 13 characters high)
center: 7 (center of bottom to center of top is 14)

···

On 6/14/08, ThoML <micathom@gmail.com> wrote:

Hi,

> For example:
>
> ruby circle.rb 7
>
> Should produce a circle of radius 7

I'm not sure if this is intentional but the circle is 15 characters
high. Of course, the line has to be counted in too.
Nevertheless ... :slight_smile:

Regards,
Thomas.

Oops.. I just realized that I made an error in canvas size calculation..

Here's the fixed code:

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

Andrea

Here is my solution.

Aaron

class Circle
  attr_accessor :radius, :aspect_ratio

  def initialize(radius, aspect_ratio)
    self.radius = radius
    self.aspect_ratio = aspect_ratio == 0 ? 1 : aspect_ratio
  end

  def to_s
    (0..y_diameter).inject('') do |rows, y|
      rows + (0..x_diameter).inject('') do |row, x|
        row + (on_circle?(x, y) ? '#' : ' ')
      end + "\n"
    end
  end

  private

  def y_diameter
    radius * 2
  end

  def x_diameter
    y_diameter * aspect_ratio
  end

  def on_circle?(x,y)
    Math.sqrt((x/aspect_ratio-radius)**2 + (y-radius)**2).round ==
radius
  end
end

print Circle.new(ARGV[0].to_i, ARGV[1].to_f).to_s

Here's mine

# initial values
r = ARGV[0].to_i; k = 360.0/(Math::PI*2)

# if r < 1 write error and exit
puts "USAGE: circle.rb radius [with radius >= 1]"; exit if r < 1

# get some points and map them on a matrix
m = Array.new(r).collect{|e|Array.new(r).fill(" ")}
j = [0.0,90.0,*(1...p=((vr=(r-1))*2)).to_a.collect{|e|
    e*(90.0/p.to_f) }].each{|a|
    vl=[Math.sin(a/k),Math.cos(a/k)].collect{|c| (c*vr).round }
    m[vl[0]][vl[1]]="#" }
s = m.collect{|a| w=a.join(""); w.reverse+w }.join("\n")

# print the result
puts s.reverse+"\n"+s

···

On Mon, Jun 16, 2008 at 3:54 PM, Jon Garvin <jgarvin.lists@gmail.com> wrote:

Here's mine before I delved into trying to add the aspect ratio feature.

class Circle
def initialize(radius)
  @radius = radius.to_i
end

def draw
  (0..@radius*2).each do |x|
    (0..@radius*2).each do |y|
      print distance_from_center(x,y).round == @radius ? '#' : '.'
    end
    puts
  end
end

def distance_from_center(x,y)
  a = calc_side(x)
  b = calc_side(y)
  return Math.sqrt(a**2 + b**2)
end

def calc_side(z)
  z < @radius ? (@radius - z) : (z - @radius)
end
end

Circle.new(ARGV.shift).draw

--

http://www.5valleys.com/

http://www.workingwithrails.com/person/8078

--
Go outside! The graphics are amazing!

Just make test pass....

According to my watch, you're about 24 hours too early.

> > For example:

> > ruby circle.rb 7

> > Should produce a circle of radius 7

> I'm not sure if this is intentional but the circle is 15 characters
> high. Of course, the line has to be counted in too.
> Nevertheless ... :slight_smile:

> Regards,
> Thomas.

Depends on where you are measuring the radius:

outside: 7.5
inside: 6.5 (white space is 13 characters high)
center: 7 (center of bottom to center of top is 14)

It was quite intentional that my circle of radius 7 took up 15 rows of
characters. This is a common issue when dealing with computer
graphics: how do you measure distance on a field of discrete elements?

In computer graphics, this is often not a big deal when drawing 3d
objects, especially if you have blurring, other post-processing, or
anti-aliasing going on. It is much more important when you are trying
to render a HUD or UI elements, for example, that you want pixel-
perfect to the artwork provided. Many graphics cards have a setting
you can enable/disable to offset coordinates by half a pixel...
Putting it into the correct mode and setting your texturing unit to
point sampling mode (as opposed to tri-/bi-linear sampling) will give
you pixel-perfect results.

So, in the case as I presented it, I was measuring from the center of
the character cell, which is 15 rows high *if measured from the top
edge of the top row to the bottom edge of the bottom row*. But as Eric
pointed out, it's only 14 if you measure from character cell center's.