Решение на Трета задача от Илиян Бобев

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

Към профила на Илиян Бобев

Резултати

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

Код

HTML_PREFIX = <<-PREDATA
<!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">
PREDATA
HTML_SUFFIX = <<-POSTDATA
</div>
</body>
</html>
POSTDATA
module Graphics
class Canvas
attr_reader :width, :height
def initialize(width, height)
@width = width
@height = height
@points = []
end
def set_pixel(x, y)
if on_canvas?(x, y)
@points << [x, y]
end
end
def pixel_at?(x, y)
if on_canvas?(x, y)
@points.include? [x, y]
end
end
def draw(shape)
@points += shape.pixels
end
def render_as(renderer)
renderer.render(@width, @height, @points)
end
private
def on_canvas?(x, y)
x.between?(0, @width) and y.between?(0, @height)
end
end
class Renderers
class Ascii < Renderers
def self.render(width, height, points)
super("@", "-", "\n", width, height, points)
end
end
class Html < Renderers
def self.render(width, height, points)
HTML_PREFIX + super("<b></b>", "<i></i>", "<br>", width, height, points) +\
HTML_SUFFIX
end
end
private
def self.process_row(row)
row.map { |point| @points.include?(point) ? @pixel_on : @pixel_off }.join
end
def self.canvas(width, height)
0.upto(height - 1).map { |y| 0.upto(width - 1).map { |x| [x, y] } }
end
def self.render(on, off, separator, width, height, points)
@points = points
@pixel_on = on
@pixel_off = off
self.canvas(width, height).map { |row| self.process_row(row) }.join(separator)
end
end
module Shape
def eql?(other)
self == other and self.class == other.class
end
def hash
pixels.hash
end
end
class Point
attr_reader :x, :y
include Shape
def initialize(x, y)
@x = x
@y = y
end
def pixels
[[@x, @y]]
end
def ==(other)
@x == other.x and @y == other.y
end
def <=>(other)
result = @x <=> other.x
if result == 0
@y <=> other.y
else
result
end
end
end
class Line
attr_reader :from, :to
include Shape
def initialize(point_a, point_b)
if (point_a <=> point_b) > 0
@from = point_b
@to = point_a
else
@from = point_a
@to = point_b
end
end
def ==(other)
@from == other.from and @to == other.to
end
def pixels
delta_x, delta_y = @to.x - @from.x, @to.y - @from.y
if delta_x != 0
slope = delta_y/delta_x.to_f
end
if slope
rasterize([delta_x, delta_y].max, slope).map { |x, y| [from.x + x, from.y + y] }
else
@from.y.upto(@to.y).map { |y| [@from.x, y] }
end
end
private
def rasterize(length, slope)
if slope > 1
0.upto(length).map { |i| [(i/slope).round, i] }
else
0.upto(length).map { |i| [i, (i*slope).round] }
end
end
end
class Rectangle
attr_reader :left, :right, :top_left, :top_right, :bottom_right, :bottom_left
include Shape
def initialize(point_a, point_b)
if (point_a <=> point_b) > 0
@left = point_b
@right = point_a
else
@left = point_a
@right = point_b
end
determine_corners
end
def ==(other)
@top_left == other.top_left and @bottom_right == other.bottom_right
end
def pixels
[
[@top_left , @top_right],
[@top_right , @bottom_right],
[@bottom_right, @bottom_left],
[@bottom_left , @top_left]
].map { |points| Line.new *points }.map(&:pixels).reduce &:+
end
private
def corner_setter top_left, top_right, bottom_right, bottom_left
@top_left = top_left
@top_right = top_right
@bottom_right = bottom_right
@bottom_left = bottom_left
end
def determine_corners
other_left = Point.new @left.x, @right.y
other_right = Point.new @right.x, @left.y
if @left.y < other_left.y
corner_setter @left, other_right, @right, other_left
else
corner_setter other_left, @right, other_right, @left
end
end
end
end

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

.....................................................................

Finished in 0.10573 seconds
69 examples, 0 failures

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

Илиян обнови решението на 20.12.2013 18:18 (преди над 10 години)

+HTML_PREFIX = <<-PREDATA
+<!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">
+PREDATA
+
+HTML_SUFFIX = <<-POSTDATA
+ </div>
+</body>
+</html>
+POSTDATA
+
+module Graphics
+ class Canvas
+ attr_reader :width, :height
+
+ def initialize(width, height)
+ @width = width
+ @height = height
+ @points = []
+ end
+
+ def set_pixel(x, y)
+ if on_canvas?(x, y)
+ @points << [x, y]
+ end
+ end
+
+ def pixel_at?(x, y)
+ if on_canvas?(x, y)
+ @points.include? [x, y]
+ end
+ end
+
+ def draw(shape)
+ @points += shape.pixels
+ end
+
+ def render_as(renderer)
+ renderer.render(@width, @height, @points)
+ end
+
+ private
+
+ def on_canvas?(x, y)
+ x.between?(0, @width) and y.between?(0, @height)
+ end
+ end
+
+ class Renderers
+ class Ascii < Renderers
+ def self.render(width, height, points)
+ super("@", "-", "\n", width, height, points)
+ end
+ end
+
+ class Html < Renderers
+ def self.render(width, height, points)
+ HTML_PREFIX + super("<b></b>", "<i></i>", "<br>", width, height, points) +\
+ HTML_SUFFIX
+ end
+ end
+
+ private
+
+ def self.process_row(row)
+ row.map { |point| @points.include?(point) ? @pixel_on : @pixel_off }.join
+ end
+
+ def self.canvas(width, height)
+ 0.upto(height - 1).map { |y| 0.upto(width - 1).map { |x| [x, y] } }
+ end
+
+ def self.render(on, off, separator, width, height, points)
+ @points = points
+ @pixel_on = on
+ @pixel_off = off
+ self.canvas(width, height).map { |row| self.process_row(row) }.join(separator)
+ end
+ end
+
+ module Shape
+ def eql?(other)
+ self == other and self.class == other.class
+ end
+
+ def hash
+ pixels.hash
+ end
+ end
+
+ class Point
+ attr_reader :x, :y
+ include Shape
+
+ def initialize(x, y)
+ @x = x
+ @y = y
+ end
+
+ def pixels
+ [[@x, @y]]
+ end
+
+ def ==(other)
+ @x == other.x and @y == other.y
+ end
+
+ def <=>(other)
+ result = @x <=> other.x
+ if result == 0
+ @y <=> other.y
+ else
+ result
+ end
+ end
+ end
+
+ class Line
+ attr_reader :from, :to
+ include Shape
+
+ def initialize(point_a, point_b)
+ if (point_a <=> point_b) > 0
+ @from = point_b
+ @to = point_a
+ else
+ @from = point_a
+ @to = point_b
+ end
+ end
+
+ def ==(other)
+ @from == other.from and @to == other.to
+ end
+
+ def pixels
+ delta_x, delta_y = @to.x - @from.x, @to.y - @from.y
+ if delta_x != 0
+ slope = delta_y/delta_x.to_f
+ end
+ if slope
+ rasterize([delta_x, delta_y].max, slope).map { |x, y| [from.x + x, from.y + y] }
+ else
+ @from.y.upto(@to.y).map { |y| [@from.x, y] }
+ end
+ end
+
+ private
+
+ def rasterize(length, slope)
+ if slope > 1
+ 0.upto(length).map { |i| [(i/slope).round, i] }
+ else
+ 0.upto(length).map { |i| [i, (i*slope).round] }
+ end
+ end
+ end
+
+ class Rectangle
+ attr_reader :left, :right, :top_left, :top_right, :bottom_right, :bottom_left
+ include Shape
+
+ def initialize(point_a, point_b)
+ if (point_a <=> point_b) > 0
+ @left = point_b
+ @right = point_a
+ else
+ @left = point_a
+ @right = point_b
+ end
+ determine_corners
+ end
+
+ def ==(other)
+ @top_left == other.top_left and @bottom_right == other.bottom_right
+ end
+
+ def pixels
+ [
+ [@top_left , @top_right],
+ [@top_right , @bottom_right],
+ [@bottom_right, @bottom_left],
+ [@bottom_left , @top_left]
+ ].map { |points| Line.new *points }.map(&:pixels).reduce &:+
+ end
+
+ private
+
+ def corner_setter top_left, top_right, bottom_right, bottom_left
+ @top_left = top_left
+ @top_right = top_right
+ @bottom_right = bottom_right
+ @bottom_left = bottom_left
+ end
+
+ def determine_corners
+ other_left = Point.new @left.x, @right.y
+ other_right = Point.new @right.x, @left.y
+ if @left.y < other_left.y
+ corner_setter @left, other_right, @right, other_left
+ else
+ corner_setter other_left, @right, other_right, @left
+ end
+ end
+ end
+end

Бележки:

  • Константите с HTML кода е добре да са вътре в именуваното пространство, а не да замърсяват глобалното такова. А най-добре да са в Graphics::Renderers::Html, където им е тематично мястото.
  • "Prefix" и "suffix" са по-неподходящи имена, отколкото "header" и "footer" в случая.
  • Помисли дали няма друг по-оптимален начин в Ruby за реализация вътрешното представяне на пано от масив от масиви. Ruby не е C и има по-удобни структури от данни :)
  • В рендерерите държиш state в клас, което не е добра идея; по-добре се откажи от класовите методи и се възползвай от факта, че имаш класове – те са идеални за да държат състояние :)
  • На няколко места в рендерите имаш излишни употреби на self..
  • Може да ползваш синоними на методи, за да реализираш eql?. Не е нужно да имат различна дефиниция. Няма да сравняваме точка с линия в тестовете.
  • На база на това какво съдържа, модулът Shape не е именуван подходящо.
  • Няма нужда да дефинираш <=>, условието не го изисква. А и този метод не е особено полезен, ако не направиш include Comparable в класа. След това би могъл да ползваш много по-интуитивното point_a > point_b, вместо point_a <=> point_b > 0.
  • Слагай include-ите преди attr_* методите – конвенция.
  • Оставяй празни редове тук-там за четимост, те не се броят в редовете в метод. Например, аз бих сложил празни редове м/у 163 и 164, както и м/у 160 и 161, 193 и 194...
  • Сложи скоби около дефиницията на параметрите на corner_setter.
  • Ето как бих форматирал Rectangle#pixels:

      [
        [@top_left,     @top_right   ],
        [@top_right,    @bottom_right],
        [@bottom_right, @bottom_left ],
        [@bottom_left,  @top_left    ],
      ].map { |a, b| Line.new a, b }.map(&:pixels).reduce &:+
    

    Но това е по-скоро въпрос на вкус.

  • corner_setter е кофти име. По-добре corners= (self.corners = ...), или поне set_corners.