Кристиан обнови решението на 22.12.2013 20:49 (преди почти 11 години)
+require 'set'
+
+module Graphics
+ module Renderers
+ module Ascii
+ end
+ module Html
+ end
+ end
+
+ class Canvas
+ HTML_TEMPLATE = "<!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\">
+@canvas@
+ </div>
+ </body>
+</html>"
+
+ attr_reader :width
+ attr_reader :height
+ def initialize(width, height)
+ @pixels = Set.new []
+ @width = width
+ @height = height
+ end
+
+ def set_pixel(x, y)
+ @pixels.add [x, y]
+ end
+
+ def pixel_at?(x, y)
+ @pixels.include? [x, y]
+ end
+
+ def draw(figure)
+ draw_point(figure) if figure.instance_of? Point
+ draw_line(figure) if figure.instance_of? Line
+ draw_rectangle(figure) if figure.instance_of? Rectangle
+ end
+
+ def draw_point(point)
+ set_pixel(point.x, point.y)
+ end
+
+ def draw_rectangle(rectangle)
+ draw_line(Line.new(rectangle.top_left, rectangle.top_right))
+ draw_line(Line.new(rectangle.top_left, rectangle.bottom_left))
+ draw_line(Line.new(rectangle.top_right, rectangle.bottom_right))
+ draw_line(Line.new(rectangle.bottom_left, rectangle.bottom_right))
+ end
+
+ def draw_line(line)
+ set_pixel(line.from.x, line.from.y)
+ bresenham(line)
+ end
+
+ def bresenham(line)
+ x, y, delta_x, delta_y, step_x, step_y, error = bresenham_initial_variables(line)
+ until (x == line.to.x && y == line.to.y)
+ x, y = 2 * error >= delta_y ? [x + step_x, y] : [x, y + step_y]
+ error = 2 * error >= delta_y ? error + delta_y : error + delta_x
+ set_pixel(x, y)
+ end
+ end
+
+ def bresenham_initial_variables(line)
+ delta_x = (line.to.x - line.from.x).abs
+ delta_y = -(line.to.y - line.from.y).abs
+ step_x = line.from.x < line.to.x ? 1 : -1
+ step_y = line.from.y < line.to.y ? 1 : -1
+ error = delta_x + delta_y
+ [line.from.x, line.from.y, delta_x, delta_y, step_x, step_y, error]
+ end
+
+ def render_as(renderer)
+ if renderer == Renderers::Ascii
+ render_with_symbols('@', '-', "\n")
+ elsif renderer == Renderers::Html
+ template = HTML_TEMPLATE
+ template.gsub("@canvas@", render_with_symbols("<b></b>", "<i></i>", "<br>\n"))
+ end
+ end
+
+ def render_with_symbols(filled, empty, line_end)
+ rows = 0.upto(width * height - 1).each_slice(width).map do |row|
+ row.map do |pixel|
+ symbol_for_pixel(pixel.remainder(width), pixel.div(width), filled, empty)
+ end
+ end
+ rows.map(&:join).join(line_end)
+ end
+
+ def symbol_for_pixel(x, y, filled, empty)
+ pixel_at?(x, y) ? filled : empty
+ end
+ end
+
+ class Point
+ attr_reader :x
+ attr_reader :y
+
+ def initialize(x, y)
+ @x = x
+ @y = y
+ end
+
+ def ==(other)
+ x == other.x && y == other.y
+ end
+
+ def eql?(other)
+ self == other
+ end
+
+ def hash
+ x.hash ^ y.hash
+ end
+
+ def left_compared_to?(other)
+ return true if x < other.x
+ return false if x > other.x
+ return y < other.y
+ end
+ end
+
+ class Line
+ attr_reader :from
+ attr_reader :to
+ def initialize(from, to)
+ from, to = to, from if to.left_compared_to? from
+ @from = from
+ @to = to
+ end
+
+ def ==(other)
+ (from == other.from) && (to == other.to)
+ end
+
+ def eql?(other)
+ self == other
+ end
+
+ def hash
+ @from.hash ^ @to.hash
+ end
+ end
+
+ class Rectangle
+ attr_reader :left
+ attr_reader :right
+ attr_reader :top_left
+ attr_reader :top_right
+ attr_reader :bottom_left
+ attr_reader :bottom_right
+
+ def initialize(a, b)
+ @left = Line.new(a, b).from
+ @right = Line.new(a, b).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 ==(other)
+ top_left == other.top_left &&
+ top_right == other.top_right &&
+ bottom_left == other.bottom_left &&
+ bottom_right == other.bottom_right
+ end
+
+ def eql?(other)
+ self == other
+ end
+
+ def hash
+ @top_left.hash ^ @top_right.hash ^ @bottom_left.hash ^ @bottom_right.hash
+ end
+ end
+end