Решение на Трета задача от Иван Капукаранов

Обратно към всички решения

Към профила на Иван Капукаранов

Резултати

  • 6 точки от тестове
  • 0 бонус точки
  • 6 точки общо
  • 68 успешни тест(а)
  • 1 неуспешни тест(а)

Код

module Graphics
module Renderers
class Html
def self.header
"<!DOCTYPE html>
<html>
<head>
<title>Rendered Canvas</title>
<style type=\"text/css\">
.canvas {
font-size: 1px;
line-height: 1px;
}
.canvas * {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 5px;
}
.canvas i {
background-color: #eee;
}
.canvas b {
background-color: #333;
}
</style>
</head>
<body>
<div class=\"canvas\">\n"
end
def self.footer
" </div>
</body>
</html>"
end
end
class Ascii
end
end
class Canvas
attr_reader :width, :height
def initialize(width, height)
@width = width
@height = height
@world = Array.new(height).map { |height| height = Array.new(width) }
@rendered = ""
end
def set_pixel(x, y)
@world[y][x] = true
end
def pixel_at?(x, y)
@world[y][x]
end
def draw(object)
object.set_points
object.coordinates.each { |point| @world[point.last][point.first] = true }
end
def render_as(render)
if render == Graphics::Renderers::Ascii
render_as_ascii(render, "\n", "@", "-")
elsif render == Graphics::Renderers::Html
render_as_html(render, "<br>\n", "<b></b>", "<i></i>")
end
end
def render_as_ascii(render, new_line, point, empty)
@rendered = ""
render_line_as(new_line, point, empty)
@rendered.strip
end
def render_as_html(render, new_line, point, empty)
@rendered = ""
render_line_as(new_line, point, empty)
render.header + @rendered.chomp("<br>\n") + render.footer
end
def render_line_as(new_line, point, empty)
@world.each do |line|
render_line_item_as(line, point, empty)
@rendered += new_line
end
end
def render_line_item_as(line, point, empty)
line.each { |item| replace(item, point, empty) }
end
def replace(item, point, empty)
item ? @rendered += point : @rendered += empty
end
end
class Point
attr_reader :x, :y, :coordinates
def initialize(x, y)
@x = x
@y = y
@coordinates = []
end
def set_points
@coordinates << [x, y]
end
def hash
[x, y].hash
end
def eql?(other)
hash == other.hash
end
alias :== :eql?
end
class Line
attr_reader :from, :to, :coordinates
def initialize(first, second)
@coordinates = []
@from = vertical?(first, second) ? upper(first, second) : left(first, second)
@to = @from == first ? second : first
end
def upper(first, second)
first.y <= second.y ? first : second
end
def left(first, second)
first.x <= second.x ? first : second
end
def vertical?(first, second)
first.x - second.x == 0
end
def set_points
if vertical?(from, to)
from.y.upto(to.y).each { |point| @coordinates << [from.x, point] }
elsif ((to.y.to_f - from.y) / (to.x - from.x)).abs < 1
bresenham_by_column
else
bresenham_by_row
end
end
def bresenham_by_row
error = (to.x.to_f - from.x) / (to.y - from.y)
range = from.y > to.y ? to.y.upto(from.y) : from.y.upto(to.y)
range.each do |row|
@coordinates << [(error * (row - from.y) + from.x).round, row]
end
end
def bresenham_by_column
error = (to.y.to_f - from.y) / (to.x - from.x)
from.x.upto(to.x).each do |column|
@coordinates << [column, (error * (column - from.x) + from.y).round]
end
end
def hash
[from, to].hash
end
def eql?(other)
hash == other.hash
end
alias :== :eql?
end
class Rectangle
attr_reader :left, :right, :coordinates
def initialize(first, second)
@left = vertical?(first, second) ? upper(first, second) : left_point(first, second)
@right = @left == first ? second : first
@height = (right.y - left.y).abs
@width = (right.x - left.x).abs
@coordinates = []
end
def vertical?(first, second)
first.x - second.x == 0
end
def upper(first, second)
first.y <= second.y ? first : second
end
def left_point(first, second)
first.x <= second.x ? first : second
end
def top_left
right.y - left.y < 0 ? Point.new(left.x, left.y - @height) : left
end
def top_right
@width == 0 ? top_left : Point.new(top_left.x + @width, top_left.y)
end
def bottom_left
right.y - left.y < 0 ? left : Point.new(left.x, left.y + @height)
end
def bottom_right
@width == 0 ? bottom_left : Point.new(bottom_left.x + @width, bottom_left.y)
end
def set_points
top_left.y.upto(bottom_left.y).each do |point|
@coordinates << [top_left.x, point]
@coordinates << [top_right.x, point]
end
top_left.x.upto(top_right.x).each do |point|
@coordinates << [point, top_left.y]
@coordinates << [point, bottom_left.y]
end
end
def hash
[top_left, top_right, bottom_left, bottom_right].hash
end
def eql?(other)
hash == other.hash
end
alias :== :eql?
end
end

Лог от изпълнението

..........F..........................................................

Failures:

  1) Graphics Canvas drawing of shapes and rasterization of points works for multiple ones
     Failure/Error: canvas.set_pixel 4, 4
     NoMethodError:
       undefined method `[]=' for nil:NilClass
     # /tmp/d20131223-4637-p23jxn/solution.rb:54:in `set_pixel'
     # /tmp/d20131223-4637-p23jxn/spec.rb:57:in `block (5 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

Finished in 0.10731 seconds
69 examples, 1 failure

Failed examples:

rspec /tmp/d20131223-4637-p23jxn/spec.rb:51 # Graphics Canvas drawing of shapes and rasterization of points works for multiple ones

История (5 версии и 2 коментара)

Иван обнови решението на 21.12.2013 23:11 (преди почти 11 години)

+module Graphics
+ module Renderers
+ class Html
+ def self.key
+ :html
+ end
+
+ def self.start
+ "<!DOCTYPE html>
+ <html>
+ <head>
+ <title>Rendered Canvas</title>
+ <style type=\"text/css\">
+ .canvas {
+ font-size: 1px;
+ line-height: 1px;
+ }
+ .canvas * {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ border-radius: 5px;
+ }
+ .canvas i {
+ background-color: #eee;
+ }
+ .canvas b {
+ background-color: #333;
+ }
+ </style>
+ </head>
+ <body>
+ <div class=\"canvas\">\n"
+ end
+
+ def self.end
+ " </div>
+ </body>
+ </html>"
+ end
+ end
+
+ class Ascii
+ def self.key
+ :ascii
+ end
+ end
+ end
+
+ class Canvas
+ attr_reader :width, :height
+
+ def initialize(width, height)
+ @width = width
+ @height = height
+ @world = Array.new(height).map { |height| height = Array.new(width) }
+ @rendered = ""
+ end
+
+ def set_pixel(x, y)
+ @world[y][x] = true
+ end
+
+ def pixel_at?(x, y)
+ @world[y][x]
+ end
+
+ def draw(object)
+ object.set_points
+ object.coordinates.each { |point| @world[point.last][point.first] = true }
+ end
+
+ def render_as(render)
+ if render.key == :ascii
+ render_as_ascii(render, "\n", "@", "-")
+ elsif render.key == :html
+ render_as_html(render, "<br>\n", "<b></b>", "<i></i>")
+ end
+ end
+
+ def render_as_ascii(render, new_line, point, empty)
+ @rendered = ""
+ render_line_as(new_line, point, empty)
+ @rendered
+ end
+
+ def render_as_html(render, new_line, point, empty)
+ @rendered = ""
+ render_line_as(new_line, point, empty)
+ render.start + @rendered + render.end
+ end
+
+ def render_line_as(new_line, point, empty)
+ @world.each do |line|
+ render_line_item_as(line, point, empty)
+ @rendered += new_line
+ end
+ end
+
+ def render_line_item_as(line, point, empty)
+ line.each { |item| replace(item, point, empty) }
+ end
+
+ def replace(item, point, empty)
+ item ? @rendered += point : @rendered += empty
+ end
+
+ end
+
+ class Point
+ attr_reader :x, :y, :coordinates
+
+ def initialize(x, y)
+ @x = x
+ @y = y
+ @coordinates = []
+ end
+
+ def set_points
+ @coordinates << [x, y]
+ end
+ end
+
+ class Line
+ attr_reader :from, :to, :coordinates
+
+ def initialize(first, second)
+ @coordinates = []
+ @from = vertical(first, second) ? upper(first, second) : left(first, second)
+ @to = @from == first ? second : first
+ end
+
+ def upper(first, second)
+ first.y <= second.y ? first : second
+ end
+
+ def left(first, second)
+ first.x <= second.x ? first : second
+ end
+
+ def vertical(first, second)
+ first.x - second.x == 0
+ end
+
+ def set_points
+ if vertical(from, to)
+ from.y.upto(to.y).each { |point| @coordinates << [from.x, point] }
+ else
+ from.x.upto(to.x).each { |point| @coordinates << [point, from.y] }
+ end
+ end
+
+ end
+
+ class Rectangle
+ attr_reader :left, :right, :coordinates
+
+ def initialize(first, second)
+ @left = vertical(first, second) ? upper(first, second) : left_point(first, second)
+ @right = @left == first ? second : first
+ @coordinates = []
+ @height = (right.y - left.y).abs
+ @width = (right.x - left.x).abs
+ end
+
+ def vertical(first, second)
+ first.x - second.x == 0
+ end
+
+ def upper(first, second)
+ first.y <= second.y ? first : second
+ end
+
+ def left_point(first, second)
+ first.x <= second.x ? first : second
+ end
+
+ def top_left
+ right.y - left.y < 0 ? Point.new(left.y - @height, right.y) : left
+ end
+
+ def top_right
+ @width == 0 ? top_left : Point.new(top_left.x + @width, top_left.y)
+ end
+
+ def bottom_left
+ right.y - left.y < 0 ? left : Point.new(left.x, left.y + @height)
+ end
+
+ def bottom_right
+ @width == 0 ? bottom_left : Point.new(bottom_left.x + @width, bottom_left.y)
+ end
+
+ def set_points
+ top_left.y.upto(bottom_left.y).each do |point|
+ @coordinates << [top_left.x, point]
+ @coordinates << [top_right.x, point]
+ end
+
+ top_left.x.upto(top_right.x).each do |point|
+ @coordinates << [point, top_left.y]
+ @coordinates << [point, bottom_left.y]
+ end
+ end
+ end
+end

Иван обнови решението на 21.12.2013 23:55 (преди почти 11 години)

module Graphics
module Renderers
class Html
def self.key
:html
end
def self.start
"<!DOCTYPE html>
<html>
<head>
<title>Rendered Canvas</title>
<style type=\"text/css\">
.canvas {
font-size: 1px;
line-height: 1px;
}
.canvas * {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 5px;
}
.canvas i {
background-color: #eee;
}
.canvas b {
background-color: #333;
}
</style>
</head>
<body>
<div class=\"canvas\">\n"
end
def self.end
" </div>
</body>
</html>"
end
end
class Ascii
def self.key
:ascii
end
end
end
class Canvas
attr_reader :width, :height
def initialize(width, height)
@width = width
@height = height
@world = Array.new(height).map { |height| height = Array.new(width) }
@rendered = ""
end
def set_pixel(x, y)
@world[y][x] = true
end
def pixel_at?(x, y)
@world[y][x]
end
def draw(object)
object.set_points
object.coordinates.each { |point| @world[point.last][point.first] = true }
end
def render_as(render)
if render.key == :ascii
render_as_ascii(render, "\n", "@", "-")
elsif render.key == :html
render_as_html(render, "<br>\n", "<b></b>", "<i></i>")
end
end
def render_as_ascii(render, new_line, point, empty)
@rendered = ""
render_line_as(new_line, point, empty)
@rendered
end
def render_as_html(render, new_line, point, empty)
@rendered = ""
render_line_as(new_line, point, empty)
render.start + @rendered + render.end
end
def render_line_as(new_line, point, empty)
@world.each do |line|
render_line_item_as(line, point, empty)
@rendered += new_line
end
end
def render_line_item_as(line, point, empty)
line.each { |item| replace(item, point, empty) }
end
def replace(item, point, empty)
item ? @rendered += point : @rendered += empty
end
end
class Point
attr_reader :x, :y, :coordinates
def initialize(x, y)
@x = x
@y = y
@coordinates = []
end
def set_points
@coordinates << [x, y]
end
+
+ def eql?(other)
+ x == other.x and y == other.y
+ end
+
+ alias :== :eql?
+
end
class Line
attr_reader :from, :to, :coordinates
def initialize(first, second)
@coordinates = []
@from = vertical(first, second) ? upper(first, second) : left(first, second)
@to = @from == first ? second : first
end
def upper(first, second)
first.y <= second.y ? first : second
end
def left(first, second)
first.x <= second.x ? first : second
end
def vertical(first, second)
first.x - second.x == 0
end
def set_points
if vertical(from, to)
from.y.upto(to.y).each { |point| @coordinates << [from.x, point] }
else
from.x.upto(to.x).each { |point| @coordinates << [point, from.y] }
end
end
+ def eql?(other)
+ from == other.from and to == other.to
+ end
+
+ alias :== :eql?
+
end
class Rectangle
attr_reader :left, :right, :coordinates
def initialize(first, second)
@left = vertical(first, second) ? upper(first, second) : left_point(first, second)
@right = @left == first ? second : first
@coordinates = []
@height = (right.y - left.y).abs
@width = (right.x - left.x).abs
end
def vertical(first, second)
first.x - second.x == 0
end
def upper(first, second)
first.y <= second.y ? first : second
end
def left_point(first, second)
first.x <= second.x ? first : second
end
def top_left
right.y - left.y < 0 ? Point.new(left.y - @height, right.y) : left
end
def top_right
@width == 0 ? top_left : Point.new(top_left.x + @width, top_left.y)
end
def bottom_left
right.y - left.y < 0 ? left : Point.new(left.x, left.y + @height)
end
def bottom_right
@width == 0 ? bottom_left : Point.new(bottom_left.x + @width, bottom_left.y)
end
def set_points
top_left.y.upto(bottom_left.y).each do |point|
@coordinates << [top_left.x, point]
@coordinates << [top_right.x, point]
end
top_left.x.upto(top_right.x).each do |point|
@coordinates << [point, top_left.y]
@coordinates << [point, bottom_left.y]
end
end
+
+ def eql?(other)
+ top_left == other.top_left and top_right == other.top_right and
+ bottom_left == other.bottom_left and bottom_right == other.bottom_right
+ end
+
+ alias :== :eql?
+
end
end

Иван обнови решението на 22.12.2013 13:19 (преди почти 11 години)

module Graphics
module Renderers
class Html
def self.key
:html
end
def self.start
"<!DOCTYPE html>
<html>
<head>
<title>Rendered Canvas</title>
<style type=\"text/css\">
.canvas {
font-size: 1px;
line-height: 1px;
}
.canvas * {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 5px;
}
.canvas i {
background-color: #eee;
}
.canvas b {
background-color: #333;
}
</style>
</head>
<body>
<div class=\"canvas\">\n"
end
def self.end
" </div>
</body>
</html>"
end
end
class Ascii
def self.key
:ascii
end
end
end
class Canvas
attr_reader :width, :height
def initialize(width, height)
@width = width
@height = height
@world = Array.new(height).map { |height| height = Array.new(width) }
@rendered = ""
end
def set_pixel(x, y)
@world[y][x] = true
end
def pixel_at?(x, y)
@world[y][x]
end
def draw(object)
object.set_points
object.coordinates.each { |point| @world[point.last][point.first] = true }
end
def render_as(render)
if render.key == :ascii
render_as_ascii(render, "\n", "@", "-")
elsif render.key == :html
render_as_html(render, "<br>\n", "<b></b>", "<i></i>")
end
end
def render_as_ascii(render, new_line, point, empty)
@rendered = ""
render_line_as(new_line, point, empty)
@rendered
end
def render_as_html(render, new_line, point, empty)
@rendered = ""
render_line_as(new_line, point, empty)
render.start + @rendered + render.end
end
def render_line_as(new_line, point, empty)
@world.each do |line|
render_line_item_as(line, point, empty)
@rendered += new_line
end
end
def render_line_item_as(line, point, empty)
line.each { |item| replace(item, point, empty) }
end
def replace(item, point, empty)
item ? @rendered += point : @rendered += empty
end
end
class Point
attr_reader :x, :y, :coordinates
def initialize(x, y)
@x = x
@y = y
@coordinates = []
end
def set_points
@coordinates << [x, y]
end
+ def hash
+ (x + 2)**2 * 4 - 1 + (y + 3)**3 * 5 - 2
+ end
+
def eql?(other)
x == other.x and y == other.y
end
alias :== :eql?
end
class Line
attr_reader :from, :to, :coordinates
def initialize(first, second)
@coordinates = []
@from = vertical(first, second) ? upper(first, second) : left(first, second)
@to = @from == first ? second : first
end
def upper(first, second)
first.y <= second.y ? first : second
end
def left(first, second)
first.x <= second.x ? first : second
end
def vertical(first, second)
first.x - second.x == 0
end
def set_points
if vertical(from, to)
from.y.upto(to.y).each { |point| @coordinates << [from.x, point] }
+ elsif ((to.y.to_f - from.y) / (to.x - from.x)).abs < 1
+ bresenham_by_column
else
- from.x.upto(to.x).each { |point| @coordinates << [point, from.y] }
+ bresenham_by_row
end
end
+ def bresenham_by_row
+ error = (to.x.to_f - from.x) / (to.y - from.y)
+
+ range = from.y > to.y ? to.y.upto(from.y) : from.y.upto(to.y)
+
+ range.each do |row|
+ @coordinates << [(error * (row - from.y) + from.x).round, row]
+ end
+ end
+
+ def bresenham_by_column
+ error = (to.y.to_f - from.y) / (to.x - from.x)
+
+ from.x.upto(to.x).each do |column|
+ @coordinates << [column, (error * (column - from.x) + from.y).round]
+ end
+ end
+
+ def hash
+ from.hash**4 * 3 - 7 + to.hash**3 * 7 + 5
+ end
+
def eql?(other)
from == other.from and to == other.to
end
alias :== :eql?
end
class Rectangle
attr_reader :left, :right, :coordinates
def initialize(first, second)
@left = vertical(first, second) ? upper(first, second) : left_point(first, second)
@right = @left == first ? second : first
- @coordinates = []
@height = (right.y - left.y).abs
@width = (right.x - left.x).abs
+ @coordinates = []
end
def vertical(first, second)
first.x - second.x == 0
end
def upper(first, second)
first.y <= second.y ? first : second
end
def left_point(first, second)
first.x <= second.x ? first : second
end
def top_left
right.y - left.y < 0 ? Point.new(left.y - @height, right.y) : left
end
def top_right
@width == 0 ? top_left : Point.new(top_left.x + @width, top_left.y)
end
def bottom_left
right.y - left.y < 0 ? left : Point.new(left.x, left.y + @height)
end
def bottom_right
@width == 0 ? bottom_left : Point.new(bottom_left.x + @width, bottom_left.y)
end
def set_points
top_left.y.upto(bottom_left.y).each do |point|
@coordinates << [top_left.x, point]
@coordinates << [top_right.x, point]
end
top_left.x.upto(top_right.x).each do |point|
@coordinates << [point, top_left.y]
@coordinates << [point, bottom_left.y]
end
end
+ def hash
+ top_left.hash + top_right.hash + bottom_left.hash + bottom_right.hash
+ end
+
def eql?(other)
- top_left == other.top_left and top_right == other.top_right and
- bottom_left == other.bottom_left and bottom_right == other.bottom_right
+ top_left == other.top_left and top_right == other.top_right \
+ and bottom_left == other.bottom_left and bottom_right == other.bottom_right
end
alias :== :eql?
end
end

Като цяло, си се справил със задачата. Имам малко бележки по неща, над които трябва да поработиш, за да си подобриш ruby-джицуто :)

Бележки:

  • Помисли кой трбява да носи отговорността за "рендериране" на пано. Самото пано или рендерера? Помисли кой обект каква отговорност носи. Една основна идея на ОО-програмирането е да разпределиш различните отговорности между различни обекти. Стреми се всеки обект да отговаря за едно конкретно нещо и само за него. Напълно нормално е да имаш голям брой малки обекти в системата, с тясно специализирани отговорности. В момента имаш два класа за рендериране, които са празни.
  • "Start" и "end" са по-неподходящи имена, отколкото "header" и "footer" за HTML кода в тази задача.
  • В случая е удачно HTML кодът на Html да се пази в константа/и в класа.
  • Нямаш нужда от методите key на класовете за рендериране. Първо, всеки модул и клас има метод name, който ти връща името като низ. Тоест, в Canvas#render_as можеш да направиш if render.name == 'Grapics::Ascii' .... Второ, това също не е нужно, защото можеш да направиш направо следното: if render == Grapics::Ascii, сравнявайки директно обектите зад променливата и константата. Друг е въпросът доколко това е удачен дизайн, предвид казаното по-горе.
  • Помисли дали няма друг по-оптимален начин в Ruby за реализация вътрешното представяне на пано, от масив от масиви. Ruby не е C и има по-удобни структури от данни :)
  • Продължавайки темата с рендерирането, променливата @rendered е абсолютно ненужна. Спокойно можеш да я премахнеш и с много дребни корекции ще запазиш същата си рендерираща логика да работи. Също, ползвай map и join в тази логика.
  • Не мисля, че имаш нужда от метода set_points. Основна идея в Ruby е, че скоби около извикване на метод могат да се изпуснат, за да няма значение за потребителя на даден код дали дадено "поле" се връща директно (примерно, attr_reader като coordinates), или се изчислява динамично. Тоест, спокойно coordinates може да е метод, който при извикване да смята списъка с координати.
  • Може да ползваш синоними на методи, за да дефинираш единия от методите на равенството на фигури.
  • Виждам, че си си написал собствена формула за hash :) Има два проблема с това. Първият е, че "логиката" на тази формула – познанието, което тя кодира, се дублира. По-добре би било да го изведеш в общ метод някъде. Вторият проблем е, че няма нужда от това нещо, както бе загатнато в условието – може да разчиташ на факта, че вградените в Ruby типове вече си имат hash метод. Помисли дали има как да се възползваш от този факт, или поне DRY-ни логиката си.
  • Прави ми впечатление и в Line, че ползваш each плюс тъпчене на резултат в списък така, сякаш те е страх да ползваш map. Пробвай с повечко map.
  • Методът vertical изглежда като предикат. Каква е конвенцията да се кръщават предикати в Ruby? :)
  • Аз бих кръстил методи като left_point(a, b) така: leftmost_point_of(a, b), примерно. За по-добра четимост. Но в случая е най-добре да се възползваш от правилото, че left може да си е твой метод, в който правиш необходимата сметка.
  • Ако имаш методи, които се казват set_*, или get_* в Ruby, това е сигнална лампа, че нещо в дизайна ти не е наред. В Ruby няма практика да се кръщават методи така.

Иван обнови решението на 22.12.2013 17:43 (преди почти 11 години)

module Graphics
module Renderers
class Html
- def self.key
- :html
- end
-
- def self.start
+ def self.header
"<!DOCTYPE html>
<html>
<head>
<title>Rendered Canvas</title>
<style type=\"text/css\">
.canvas {
font-size: 1px;
line-height: 1px;
}
.canvas * {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 5px;
}
.canvas i {
background-color: #eee;
}
.canvas b {
background-color: #333;
}
</style>
</head>
<body>
<div class=\"canvas\">\n"
end
- def self.end
+ def self.footer
" </div>
</body>
</html>"
end
end
class Ascii
- def self.key
- :ascii
- end
end
end
class Canvas
attr_reader :width, :height
def initialize(width, height)
@width = width
@height = height
@world = Array.new(height).map { |height| height = Array.new(width) }
@rendered = ""
end
def set_pixel(x, y)
@world[y][x] = true
end
def pixel_at?(x, y)
@world[y][x]
end
def draw(object)
object.set_points
object.coordinates.each { |point| @world[point.last][point.first] = true }
end
def render_as(render)
- if render.key == :ascii
+ if render == Graphics::Renderers::Ascii
render_as_ascii(render, "\n", "@", "-")
- elsif render.key == :html
+ elsif render == Graphics::Renderers::Html
render_as_html(render, "<br>\n", "<b></b>", "<i></i>")
end
end
def render_as_ascii(render, new_line, point, empty)
@rendered = ""
render_line_as(new_line, point, empty)
@rendered
end
def render_as_html(render, new_line, point, empty)
@rendered = ""
render_line_as(new_line, point, empty)
- render.start + @rendered + render.end
+ render.header + @rendered + render.footer
end
def render_line_as(new_line, point, empty)
@world.each do |line|
render_line_item_as(line, point, empty)
@rendered += new_line
end
end
def render_line_item_as(line, point, empty)
line.each { |item| replace(item, point, empty) }
end
def replace(item, point, empty)
item ? @rendered += point : @rendered += empty
end
end
class Point
attr_reader :x, :y, :coordinates
def initialize(x, y)
@x = x
@y = y
@coordinates = []
end
def set_points
@coordinates << [x, y]
end
def hash
- (x + 2)**2 * 4 - 1 + (y + 3)**3 * 5 - 2
+ [x, y].hash
end
def eql?(other)
- x == other.x and y == other.y
+ hash == other.hash
end
alias :== :eql?
end
class Line
attr_reader :from, :to, :coordinates
def initialize(first, second)
@coordinates = []
- @from = vertical(first, second) ? upper(first, second) : left(first, second)
+ @from = vertical?(first, second) ? upper(first, second) : left(first, second)
@to = @from == first ? second : first
end
def upper(first, second)
first.y <= second.y ? first : second
end
def left(first, second)
first.x <= second.x ? first : second
end
- def vertical(first, second)
+ def vertical?(first, second)
first.x - second.x == 0
end
def set_points
- if vertical(from, to)
+ if vertical?(from, to)
from.y.upto(to.y).each { |point| @coordinates << [from.x, point] }
elsif ((to.y.to_f - from.y) / (to.x - from.x)).abs < 1
bresenham_by_column
else
bresenham_by_row
end
end
def bresenham_by_row
error = (to.x.to_f - from.x) / (to.y - from.y)
range = from.y > to.y ? to.y.upto(from.y) : from.y.upto(to.y)
range.each do |row|
@coordinates << [(error * (row - from.y) + from.x).round, row]
end
end
def bresenham_by_column
error = (to.y.to_f - from.y) / (to.x - from.x)
from.x.upto(to.x).each do |column|
@coordinates << [column, (error * (column - from.x) + from.y).round]
end
end
def hash
- from.hash**4 * 3 - 7 + to.hash**3 * 7 + 5
+ [from, to].hash
end
def eql?(other)
- from == other.from and to == other.to
+ hash == other.hash
end
alias :== :eql?
end
class Rectangle
attr_reader :left, :right, :coordinates
def initialize(first, second)
- @left = vertical(first, second) ? upper(first, second) : left_point(first, second)
+ @left = vertical?(first, second) ? upper(first, second) : left_point(first, second)
@right = @left == first ? second : first
@height = (right.y - left.y).abs
@width = (right.x - left.x).abs
@coordinates = []
end
- def vertical(first, second)
+ def vertical?(first, second)
first.x - second.x == 0
end
def upper(first, second)
first.y <= second.y ? first : second
end
def left_point(first, second)
first.x <= second.x ? first : second
end
def top_left
- right.y - left.y < 0 ? Point.new(left.y - @height, right.y) : left
+ right.y - left.y < 0 ? Point.new(left.x, left.y - @height) : left
end
def top_right
@width == 0 ? top_left : Point.new(top_left.x + @width, top_left.y)
end
def bottom_left
right.y - left.y < 0 ? left : Point.new(left.x, left.y + @height)
end
def bottom_right
@width == 0 ? bottom_left : Point.new(bottom_left.x + @width, bottom_left.y)
end
def set_points
top_left.y.upto(bottom_left.y).each do |point|
@coordinates << [top_left.x, point]
@coordinates << [top_right.x, point]
end
top_left.x.upto(top_right.x).each do |point|
@coordinates << [point, top_left.y]
@coordinates << [point, bottom_left.y]
end
end
def hash
- top_left.hash + top_right.hash + bottom_left.hash + bottom_right.hash
+ [top_left, top_right, bottom_left, bottom_right].hash
end
def eql?(other)
- top_left == other.top_left and top_right == other.top_right \
- and bottom_left == other.bottom_left and bottom_right == other.bottom_right
+ hash == other.hash
end
alias :== :eql?
end
end

Иван обнови решението на 22.12.2013 20:19 (преди почти 11 години)

module Graphics
module Renderers
class Html
def self.header
"<!DOCTYPE html>
<html>
<head>
<title>Rendered Canvas</title>
<style type=\"text/css\">
.canvas {
font-size: 1px;
line-height: 1px;
}
.canvas * {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 5px;
}
.canvas i {
background-color: #eee;
}
.canvas b {
background-color: #333;
}
</style>
</head>
<body>
<div class=\"canvas\">\n"
end
def self.footer
" </div>
</body>
</html>"
end
end
class Ascii
end
end
class Canvas
attr_reader :width, :height
def initialize(width, height)
@width = width
@height = height
@world = Array.new(height).map { |height| height = Array.new(width) }
@rendered = ""
end
def set_pixel(x, y)
@world[y][x] = true
end
def pixel_at?(x, y)
@world[y][x]
end
def draw(object)
object.set_points
object.coordinates.each { |point| @world[point.last][point.first] = true }
end
def render_as(render)
if render == Graphics::Renderers::Ascii
render_as_ascii(render, "\n", "@", "-")
elsif render == Graphics::Renderers::Html
render_as_html(render, "<br>\n", "<b></b>", "<i></i>")
end
end
def render_as_ascii(render, new_line, point, empty)
@rendered = ""
render_line_as(new_line, point, empty)
- @rendered
+ @rendered.strip
end
def render_as_html(render, new_line, point, empty)
@rendered = ""
render_line_as(new_line, point, empty)
- render.header + @rendered + render.footer
+ render.header + @rendered.chomp("<br>\n") + render.footer
end
def render_line_as(new_line, point, empty)
@world.each do |line|
render_line_item_as(line, point, empty)
@rendered += new_line
end
end
def render_line_item_as(line, point, empty)
line.each { |item| replace(item, point, empty) }
end
def replace(item, point, empty)
item ? @rendered += point : @rendered += empty
end
end
class Point
attr_reader :x, :y, :coordinates
def initialize(x, y)
@x = x
@y = y
@coordinates = []
end
def set_points
@coordinates << [x, y]
end
def hash
[x, y].hash
end
def eql?(other)
hash == other.hash
end
alias :== :eql?
end
class Line
attr_reader :from, :to, :coordinates
def initialize(first, second)
@coordinates = []
@from = vertical?(first, second) ? upper(first, second) : left(first, second)
@to = @from == first ? second : first
end
def upper(first, second)
first.y <= second.y ? first : second
end
def left(first, second)
first.x <= second.x ? first : second
end
def vertical?(first, second)
first.x - second.x == 0
end
def set_points
if vertical?(from, to)
from.y.upto(to.y).each { |point| @coordinates << [from.x, point] }
elsif ((to.y.to_f - from.y) / (to.x - from.x)).abs < 1
bresenham_by_column
else
bresenham_by_row
end
end
def bresenham_by_row
error = (to.x.to_f - from.x) / (to.y - from.y)
range = from.y > to.y ? to.y.upto(from.y) : from.y.upto(to.y)
range.each do |row|
@coordinates << [(error * (row - from.y) + from.x).round, row]
end
end
def bresenham_by_column
error = (to.y.to_f - from.y) / (to.x - from.x)
from.x.upto(to.x).each do |column|
@coordinates << [column, (error * (column - from.x) + from.y).round]
end
end
def hash
[from, to].hash
end
def eql?(other)
hash == other.hash
end
alias :== :eql?
end
class Rectangle
attr_reader :left, :right, :coordinates
def initialize(first, second)
@left = vertical?(first, second) ? upper(first, second) : left_point(first, second)
@right = @left == first ? second : first
@height = (right.y - left.y).abs
@width = (right.x - left.x).abs
@coordinates = []
end
def vertical?(first, second)
first.x - second.x == 0
end
def upper(first, second)
first.y <= second.y ? first : second
end
def left_point(first, second)
first.x <= second.x ? first : second
end
def top_left
right.y - left.y < 0 ? Point.new(left.x, left.y - @height) : left
end
def top_right
@width == 0 ? top_left : Point.new(top_left.x + @width, top_left.y)
end
def bottom_left
right.y - left.y < 0 ? left : Point.new(left.x, left.y + @height)
end
def bottom_right
@width == 0 ? bottom_left : Point.new(bottom_left.x + @width, bottom_left.y)
end
def set_points
top_left.y.upto(bottom_left.y).each do |point|
@coordinates << [top_left.x, point]
@coordinates << [top_right.x, point]
end
top_left.x.upto(top_right.x).each do |point|
@coordinates << [point, top_left.y]
@coordinates << [point, bottom_left.y]
end
end
def hash
[top_left, top_right, bottom_left, bottom_right].hash
end
def eql?(other)
hash == other.hash
end
alias :== :eql?
end
end