Георги обнови решението на 22.12.2013 14:09 (преди почти 11 години)
+module Graphics
+ class Canvas
+ EMPTY_PIXEL = 0
+ FULL_PIXEL = 1
+
+ attr_reader :width, :height
+
+ def initialize(width, height)
+ @width, @height = width, height
+
+ @pixels = Array.new(width)
+ @pixels.map! { Array.new(height, EMPTY_PIXEL) }
+ end
+
+ def set_pixel(x, y)
+ @pixels[x][y] = FULL_PIXEL
+ end
+
+ def pixel_at?(x, y)
+ @pixels[x][y] == FULL_PIXEL
+ end
+
+ def draw(figure)
+ figure.to_points.each { |point| set_pixel(point.x, point.y) }
+ end
+
+ def render_as(renderer)
+ renderer.render(self)
+ end
+ end
+
+ class Point
+ include Comparable
+ attr_reader :x, :y
+
+ def initialize(x, y)
+ @x, @y = x, y
+ end
+
+ def <=>(other)
+ comparison = x <=> other.x
+
+ if comparison.nonzero?
+ comparison
+ else
+ y <=> other.y
+ end
+ end
+
+ alias :eql? :==
+
+ def hash
+ [@x, @y].hash
+ end
+
+ def to_points
+ [self]
+ end
+ end
+
+ class Figure
+ def ==(other)
+ sorted_vertices == other.sorted_vertices
+ end
+
+ alias :eql? :==
+
+ def hash
+ sorted_vertices.hash
+ end
+ end
+
+ class Line < Figure
+ module Bresenham
+ private
+
+ def step_x
+ from.x < to.x ? 1 : -1
+ end
+
+ def step_y
+ from.y < to.y ? 1 : -1
+ end
+
+ def bresenham
+ delta_x, delta_y = (to.x - from.x).abs, -(to.y - from.y).abs
+ error = delta_x + delta_y
+ [from, to] + bresenham_loop(from.x, to.x, from.y, to.y, delta_x, delta_y, error)
+ end
+
+ def bresenham_loop(from_x, to_x, from_y, to_y, delta_x, delta_y, error)
+ until from_x == to_x && from_y == to_y
+ error, from_x = error + delta_y, from_x + step_x if 2 * error >= delta_y
+ error, from_y = error + delta_x, from_y + step_y if 2 * error <= delta_x
+ (result ||= []) << Point.new(from_x, from_y)
+ end
+
+ result
+ end
+ end
+
+ include Bresenham
+ attr_reader :from, :to
+
+ # point1, point2 makes more sense to me for naming the arguments below
+ # skeptic forbids that
+ def initialize(first, second)
+ @from, @to = [first, second].sort
+ end
+
+ def to_points
+ bresenham
+ end
+
+ protected
+
+ def sorted_vertices
+ [from, to].sort
+ end
+ end
+
+ class Rectangle < Figure
+ attr_reader :left, :right, :top_left, :top_right, :bottom_left, :bottom_right
+
+ def initialize(first, second)
+ @left, @right = [first, second].sort
+ @top_left, @bottom_left = [@left, Point.new(left.x, right.y)].sort
+ @top_right, @bottom_right = [@right, Point.new(right.x, left.y)].sort
+ end
+
+ def to_points
+ edges.map(&:to_points).flatten(1)
+ end
+
+ def edges
+ [Line.new(top_left, top_right),
+ Line.new(top_left, bottom_left),
+ Line.new(bottom_right, bottom_left),
+ Line.new(bottom_right, top_right)]
+ end
+
+ protected
+
+ def sorted_vertices
+ [top_left, top_right, bottom_left, bottom_right].sort
+ end
+ end
+
+ module Renderers
+ module Render
+ def render_output(canvas)
+ output = []
+ 0.upto(canvas.height - 1).each do |y|
+ 0.upto(canvas.width - 1).each do |x|
+ output << pixel_at(canvas, x, y)
+ end
+ output << new_line
+ end
+
+ output[0..-2].to_a.join
+ end
+
+ def pixel_at(canvas, x, y)
+ canvas.pixel_at?(x, y) ? full_pixel : blank_pixel
+ end
+ end
+
+ class Ascii
+ class << self
+ include Render
+
+ def full_pixel
+ '@'.freeze
+ end
+
+ def blank_pixel
+ '-'.freeze
+ end
+
+ def new_line
+ "\n".freeze
+ end
+
+ alias :render :render_output
+ end
+ end
+
+ class Html
+ class << self
+ HTML_BEGINNING = '
+ <!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">
+ '.freeze
+
+ HTML_ENDING = '
+ </div>
+ </body>
+ </html>
+ '.freeze
+
+ include Render
+
+ def full_pixel
+ '<b></b>'.freeze
+ end
+
+ def blank_pixel
+ '<i></i>'.freeze
+ end
+
+ def new_line
+ '<br>'.freeze
+ end
+
+ def render(canvas)
+ HTML_BEGINNING + render_output(canvas) + HTML_ENDING
+ end
+ end
+ end
+ end
+end