Калоян обнови решението на 21.12.2013 00:25 (преди почти 11 години)
+module Graphics
+ class Point
+ attr_reader :x, :y
+
+ def initialize(x, y)
+ @x, @y = x, y
+ end
+
+ def draw_on(canvas)
+ canvas.set_pixel @x, @y
+ end
+
+ def eql?(other)
+ @x == other.x and @y == other.y
+ end
+
+ alias_method :==, :eql?
+
+ def hash()
+ @x.hash + @y.hash
+ end
+ end
+
+ class Line
+ class BresenhamAlgorithm
+ def initialize(from, to)
+ prepare_coordinates_and_steep(from.x, from.y, to.x, to.y)
+ prepare_run_time_variables()
+ end
+
+ def apply_on(canvas)
+ @steps.each do |x|
+ canvas.set_pixel (@steep ? @y : x), (@steep ? x : @y)
+ @error -= @delta_y
+ @y += @y_step if @error < 0
+ @error += @delta_x if @error < 0
+ end
+ end
+
+ private
+ def prepare_coordinates_and_steep(from_x, from_y, to_x, to_y)
+ @steep = (to_y - from_y).abs > (to_x - from_x).abs
+ if @steep
+ @from_x, @from_y, @to_x, @to_y = from_y, from_x, to_y, to_x
+ else
+ @from_x, @from_y, @to_x, @to_y = from_x, from_y, to_x, to_y
+ end
+ end
+
+ def prepare_run_time_variables()
+ @delta_x, @delta_y = (@to_x - @from_x).abs, (@to_y - @from_y).abs
+ @error = @delta_x / 2
+ @y_step = @from_y < @to_y ? 1 : -1
+ @y = @from_y
+ @steps = @from_x < @to_x ? @from_x.upto(@to_x) : @from_x.downto(@to_x)
+ end
+ end
+
+ attr_reader :from, :to
+
+ def initialize(from, to)
+ if from.x > to.x or (from.x == to.x and from.y > to.y)
+ @from, @to = to, from
+ else
+ @from, @to = from, to
+ end
+ end
+
+ def draw_on(canvas)
+ algorithm = BresenhamAlgorithm.new @to, @from
+ algorithm.apply_on canvas
+ end
+
+ def eql?(other)
+ @from == other.from and @to == other.to
+ end
+
+ alias_method :==, :eql?
+
+ def hash()
+ @from.hash + @to.hash
+ end
+ end
+
+ class Rectangle
+ attr_reader :left, :right
+ attr_reader :top_left, :top_right, :bottom_left, :bottom_right
+
+ def initialize(left, right)
+ diagonal = Line.new left, right
+ @left, @right = diagonal.from, diagonal.to
+ @top_left = Point.new [left.x, right.x].min, [left.y, right.y].min
+ @top_right = Point.new [left.x, right.x].max, [left.y, right.y].min
+ @bottom_left = Point.new [left.x, right.x].min, [left.y, right.y].max
+ @bottom_right = Point.new [left.x, right.x].max, [left.y, right.y].max
+ end
+
+ def draw_on(canvas)
+ Line.new(@top_left, @top_right).draw_on canvas
+ Line.new(@bottom_left, @bottom_right).draw_on canvas
+ Line.new(@top_left, @bottom_left).draw_on canvas
+ Line.new(@top_right, @bottom_right).draw_on canvas
+ end
+
+ def eql?(other)
+ @left == other.left and @right == other.right
+ end
+
+ alias_method :==, :eql?
+
+ def hash()
+ @left.hash + @right.hash
+ end
+ end
+
+ module Renderers
+ module Ascii
+ def self.render(canvas)
+ canvas.canvas.map do |row|
+ row.map { |filled| fill_to_string filled }.join
+ end.join("\n")
+ end
+
+ def self.fill_to_string(filled)
+ filled ? '@' : '-'
+ end
+ end
+
+ module Html
+ def self.render(canvas)
+ wrappers = DATA.read.split('PIXEL_DATA')
+ wrappers[0] + canvas.canvas.map do |row|
+ row.map { |filled| fill_to_string filled }.join
+ end.join("<br>\n") + wrappers[1]
+ end
+
+ def self.fill_to_string(filled)
+ filled ? '<b></b>' : '<i></i>'
+ end
+ end
+ end
+
+ class Canvas
+ attr_reader :width, :height, :canvas
+
+ def initialize(width, height)
+ @width = width
+ @height = height
+ @canvas = Array.new(height) { Array.new(width, false) }
+ end
+
+ def set_pixel(x, y)
+ @canvas[y][x] = true
+ end
+
+ def pixel_at?(x, y)
+ @canvas[y][x]
+ end
+
+ def draw(figure)
+ figure.draw_on(self)
+ end
+
+ def render_as(renderer)
+ renderer.render(self)
+ end
+ end
+end
+
+__END__
+
+<!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">
+PIXEL_DATA
+</div>
+</body>
+</html>
Дизайнът ти ми харесва доста. Поздравления!
Малко бележки:
- Пропускай скобите, когато дефинириаш или извикваш метод без аргументи.
- Оставяй по един празен ред след
private
. - Бих написал
Line#draw_on
на един ред:BresenhamAlgorithm.new(@to, @from).apply_on(canvas)
. -
prepare_coordinates_and_steep
→prepare_coordinates_and_detect_steepness
. -
prepare_run_time_variables
също може да се подобри някак. Пробвай. На първо време ми идват наум неща катоprepare_deltas_and_initial_values
. - На методите в
BresenhamAlgorithm
им липсва малко вертикална подредба на операциите, за да се подобри четимостта. Същото важи и за някои други сходни по визуална "тежест" методи. - В
BresenhamAlgorithm#apply_on
може да обединиш ред 35 и 36 в единif
, мисля. - Може би някой
each
вRectangle#draw_on
? - Помисли дали няма друг по-оптимален начин в Ruby за реализация вътрешното представяне на пано от масив от масиви. Ruby не е C и има по-удобни структури от данни :)
- Потърси по-добро име за
fill_to_string
. -
canvas.canvas
ми намирисва. Не стои добре. - Освен това, имаш малко дублираща се логика в двата рендерера, свързана с обхождането на пикселите на паното, която зависи от вътрешното представяне на пано. Ако смениш вътрешното представяне на пано, ще трябва да промениш и тези два метода, а това не е добре. Опитай да ползваш нещо, което не зависи от това вътрешно представяне. Нещо, което ползва само публичния интерфейс на пано, дефиниран в условието, например.
- Да сложиш HTML-а в
DATA
-зоната е интересна идея, но ми се струва, че тук ще е по-удачно да е в константа/и в класаHtml
. - Стреми се да викаш
join
винаги с аргументи, не разчитай на тези по подразбиране.