Решение на Трета задача от Никола Ненков

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

Към профила на Никола Ненков

Резултати

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

Код

module Graphics
class Canvas
attr_reader :width
attr_reader :height
def initialize(width, height)
@width = width
@height = height
@pixels = Hash.new(false)
end
def set_pixel(x, y)
@pixels[[x, y]] = true
end
def pixel_at?(x, y)
@pixels[[x, y]]
end
def draw(shape)
shape.to_a.each { |x, y| set_pixel(x, y) }
end
def render_as(renderer)
renderer.render(self)
end
end
module Renderers
def render(canvas)#TODO refactor
indexes = 0.upto(canvas.width.pred).to_a.product(0.upto(canvas.height.pred).to_a)
pixels = indexes.map(&:reverse).map { |x, y| canvas.pixel_at?(x, y) }
rendered_lines = pixels.map { |pixel| render_pixel(pixel) }.each_slice(canvas.width)
canvas_to_string = rendered_lines.map { |line| add_new_line(line) }.join.chomp
prefix + canvas_to_string + suffix
end
class Ascii
extend Renderers
class << self
private
def render_pixel(pixel)
pixel ? "@" : "-"
end
def add_new_line(slice)
slice << "\n"
end
def prefix
""
end
def suffix
""
end
end
end
class Html
extend Renderers
class << self
private
PREFIX = <<-html.gsub /^\s+|$\n/, ""#TODO experiment with .freeze
<!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">
html
SUFFIX = <<-html.gsub /^\s+|$\n/, ""#TODO experiment with .freeze
</div>
</body>
</html>
html
def render_pixel(pixel)
pixel ? "<b></b>" : "<i></i>"
end
def add_new_line(slice)
slice << "<br>"
end
def prefix
PREFIX
end
def suffix
SUFFIX
end
end
end
end
class Point
attr_reader :x
attr_reader :y
def initialize(x, y)
@x = x
@y = y
end
def eql?(other)
[@x, @y].eql?([other.x, other.y])
end
def hash
[@x, @y].hash
end
alias_method :==, :eql?
def to_a
[[@x, @y]]
end
end
class Line
attr_reader :from
attr_reader :to
def initialize(first_point, second_point)
if first_point.x != second_point.x
@from, @to = [first_point, second_point].minmax_by(&:x)
else
@from, @to = [first_point, second_point].minmax_by(&:y)
end
end
def eql?(other)
[@from, @to].eql?([other.from, other.to])
end
def hash
[@from, @to].hash
end
alias_method :==, :eql?
def to_a
line_coordinates(@from.x, @from.y, @to.x, @to.y)
end
private
def line_coordinates(start_x, start_y, end_x, end_y)
delta_x = end_x - start_x
delta_y = end_y - start_y
if delta_x >= delta_y
bresenham(delta_x, delta_y).map { |x, y| [x + start_x, y + start_y] }
else
bresenham(delta_y, delta_x).map { |y, x| [x + start_x, y + start_y] }
end
end
def bresenham(x, y)#TODO rename height and n
height = x
0.upto(x).map do |n|
coordinates = [n, height / (2 * x)]
height += 2 * y
coordinates
end
end
end
class Rectangle
attr_reader :left
attr_reader :right
def initialize(first_point, second_point)
if first_point.x != second_point.x
@left, @right = [first_point, second_point].minmax_by(&:x)
else
@left, @right = [first_point, second_point].minmax_by(&:y)
end
end
def top_left
Point.new([@left.x, @right.x].min, [@left.y, @right.y].min)
end
def top_right
Point.new([@left.x, @right.x].min, [@left.y, @right.y].max)
end
def bottom_left
Point.new([@left.x, @right.x].max, [@left.y, @right.y].min)
end
def bottom_right
Point.new([@left.x, @right.x].max, [@left.y, @right.y].max)
end
def eql?(other)
[@left, @right].eql?([other.left, other.right])
end
def hash
[@left, @right].hash
end
alias_method :==, :eql?
def to_a
all_pixels = []
all_pixels.concat(Line.new(top_left, top_right).to_a)
all_pixels.concat(Line.new(top_right, bottom_right).to_a)
all_pixels.concat(Line.new(bottom_right, bottom_left).to_a)
all_pixels.concat(Line.new(bottom_left, top_left).to_a)
all_pixels.uniq
end
end
end

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

...........F.F.FFFFFF..F..F.................................F..F..F.F

Failures:

  1) Graphics Canvas drawing of shapes and rasterization of lines works with simple horizontal lines
     Failure/Error: ascii.should eq rendering(expected)
       
       expected: "--------\n---@@@@-\n--------"
            got: "--------\n--------\n--------"
       
       (compared using ==)
       
       Diff:
       @@ -1,4 +1,4 @@
        --------
       ----@@@@-
       +--------
        --------
     # /tmp/d20131223-4637-488e8s/spec.rb:624:in `check_rendering_of'
     # /tmp/d20131223-4637-488e8s/spec.rb:73: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)>'

  2) Graphics Canvas drawing of shapes and rasterization of lines works with lines with a small slope
     Failure/Error: ascii.should eq rendering(expected)
       
       expected: "----------\n-@@-------\n---@@@@---\n-------@@-\n----------"
            got: "------@@--\n---@@-----\n----------\n----------\n----------"
       
       (compared using ==)
       
       Diff:
       
       @@ -1,6 +1,6 @@
       +------@@--
       +---@@-----
        ----------
       --@@-------
       ----@@@@---
       --------@@-
       +----------
        ----------
     # /tmp/d20131223-4637-488e8s/spec.rb:624:in `check_rendering_of'
     # /tmp/d20131223-4637-488e8s/spec.rb:100: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)>'

  3) Graphics Canvas drawing of shapes and rasterization of lines works with multiple lines
     Failure/Error: ascii.should eq rendering(expected)
       
       expected: "-@--------\n-@@-------\n-@-@@@@---\n-@-----@@-\n----------"
            got: "-@----@@--\n-@-@@-@---\n----------\n----------\n----------"
       
       (compared using ==)
       
       Diff:
       @@ -1,6 +1,6 @@
       --@--------
       --@@-------
       --@-@@@@---
       --@-----@@-
       +-@----@@--
       +-@-@@-@---
       +----------
       +----------
        ----------
     # /tmp/d20131223-4637-488e8s/spec.rb:624:in `check_rendering_of'
     # /tmp/d20131223-4637-488e8s/spec.rb:132: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)>'

  4) Graphics Canvas drawing of shapes and rasterization of lines draws lines with two equal ends as points
     Failure/Error: canvas.draw make_line(make_point(1, 1), make_point(1, 1))
     ZeroDivisionError:
       divided by 0
     # /tmp/d20131223-4637-488e8s/solution.rb:186:in `/'
     # /tmp/d20131223-4637-488e8s/solution.rb:186:in `block in bresenham'
     # /tmp/d20131223-4637-488e8s/solution.rb:185:in `upto'
     # /tmp/d20131223-4637-488e8s/solution.rb:185:in `each'
     # /tmp/d20131223-4637-488e8s/solution.rb:185:in `map'
     # /tmp/d20131223-4637-488e8s/solution.rb:185:in `bresenham'
     # /tmp/d20131223-4637-488e8s/solution.rb:177:in `line_coordinates'
     # /tmp/d20131223-4637-488e8s/solution.rb:168:in `to_a'
     # /tmp/d20131223-4637-488e8s/solution.rb:21:in `draw'
     # /tmp/d20131223-4637-488e8s/spec.rb:143: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)>'

  5) Graphics Canvas drawing of shapes and rasterization of rectangles works with simple rects
     Failure/Error: ascii.should eq rendering(expected)
       
       expected: "----------\n-@@@@@@@@-\n-@------@-\n-@@@@@@@@-\n----------"
            got: "------@@@@\n-@----@@@@\n----------\n----------\n----------"
       
       (compared using ==)
       
       Diff:
       
       @@ -1,6 +1,6 @@
       +------@@@@
       +-@----@@@@
        ----------
       --@@@@@@@@-
       --@------@-
       --@@@@@@@@-
       +----------
        ----------
     # /tmp/d20131223-4637-488e8s/spec.rb:624:in `check_rendering_of'
     # /tmp/d20131223-4637-488e8s/spec.rb:158: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)>'

  6) Graphics Canvas drawing of shapes and rasterization of rectangles works with rects defined with their bottom left and top right points
     Failure/Error: ascii.should eq rendering(expected)
       
       expected: "----------\n-@@@@@@@@-\n-@------@-\n-@@@@@@@@-\n----------"
            got: "------@@@@\n-@----@@@@\n----------\n----------\n----------"
       
       (compared using ==)
       
       Diff:
       
       @@ -1,6 +1,6 @@
       +------@@@@
       +-@----@@@@
        ----------
       --@@@@@@@@-
       --@------@-
       --@@@@@@@@-
       +----------
        ----------
     # /tmp/d20131223-4637-488e8s/spec.rb:624:in `check_rendering_of'
     # /tmp/d20131223-4637-488e8s/spec.rb:171: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)>'

  7) Graphics Canvas drawing of shapes and rasterization of rectangles works with rects with a zero height as a line
     Failure/Error: canvas.draw make_rectangle(make_point(1, 1), make_point(8, 1))
     ZeroDivisionError:
       divided by 0
     # /tmp/d20131223-4637-488e8s/solution.rb:186:in `/'
     # /tmp/d20131223-4637-488e8s/solution.rb:186:in `block in bresenham'
     # /tmp/d20131223-4637-488e8s/solution.rb:185:in `upto'
     # /tmp/d20131223-4637-488e8s/solution.rb:185:in `each'
     # /tmp/d20131223-4637-488e8s/solution.rb:185:in `map'
     # /tmp/d20131223-4637-488e8s/solution.rb:185:in `bresenham'
     # /tmp/d20131223-4637-488e8s/solution.rb:177:in `line_coordinates'
     # /tmp/d20131223-4637-488e8s/solution.rb:168:in `to_a'
     # /tmp/d20131223-4637-488e8s/solution.rb:233:in `to_a'
     # /tmp/d20131223-4637-488e8s/solution.rb:21:in `draw'
     # /tmp/d20131223-4637-488e8s/spec.rb:182: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)>'

  8) Graphics Canvas drawing of shapes and rasterization of rectangles works with rects with a zero width and height as a single point
     Failure/Error: canvas.draw make_rectangle(make_point(1, 1), make_point(1, 1))
     ZeroDivisionError:
       divided by 0
     # /tmp/d20131223-4637-488e8s/solution.rb:186:in `/'
     # /tmp/d20131223-4637-488e8s/solution.rb:186:in `block in bresenham'
     # /tmp/d20131223-4637-488e8s/solution.rb:185:in `upto'
     # /tmp/d20131223-4637-488e8s/solution.rb:185:in `each'
     # /tmp/d20131223-4637-488e8s/solution.rb:185:in `map'
     # /tmp/d20131223-4637-488e8s/solution.rb:185:in `bresenham'
     # /tmp/d20131223-4637-488e8s/solution.rb:177:in `line_coordinates'
     # /tmp/d20131223-4637-488e8s/solution.rb:168:in `to_a'
     # /tmp/d20131223-4637-488e8s/solution.rb:233:in `to_a'
     # /tmp/d20131223-4637-488e8s/solution.rb:21:in `draw'
     # /tmp/d20131223-4637-488e8s/spec.rb:193: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)>'

  9) Graphics Renderers Ascii renders simple canvases
     Failure/Error: canvas.render_as(ascii).should eq rendering('
       
       expected: "@---\n----\n---@"
            got: "@---\n----\n----"
       
       (compared using ==)
       
       Diff:
       @@ -1,4 +1,4 @@
        @---
        ----
       ----@
       +----
     # /tmp/d20131223-4637-488e8s/spec.rb:256:in `block (4 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)>'

  10) Graphics Renderers Html renders simple canvases
     Failure/Error: html_rendering_of(canvas).should eq [
       
       expected: "<i></i><i></i><i></i><i></i><br><i></i><b></b><i></i><i></i><br><i></i><b></b><i></i><i></i>"
            got: "<i></i><i></i><i></i><i></i><br><b></b><i></i><i></i><b></b><br><i></i><i></i><i></i><i></i><br>"
       
       (compared using ==)
     # /tmp/d20131223-4637-488e8s/spec.rb:291:in `block (4 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)>'

  11) Graphics shapes Rectangle comparison for equality is true for rectangles defined with different diagonal corners
     Failure/Error: (a == b).should be_true
       expected: true value
            got: false
     # /tmp/d20131223-4637-488e8s/spec.rb:540: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)>'

  12) Graphics shapes Rectangle comparison for equality returns the same hash for rectangles defined with different diagonal corners
     Failure/Error: a.hash.should eq b.hash
       
       expected: -937315830
            got: -951523814
       
       (compared using ==)
     # /tmp/d20131223-4637-488e8s/spec.rb:563: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)>'

  13) Graphics shapes Rectangle corners top right
     Failure/Error: rect.top_right.x.should eq 5
       
       expected: 5
            got: 1
       
       (compared using ==)
     # /tmp/d20131223-4637-488e8s/spec.rb:583: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)>'

  14) Graphics shapes Rectangle corners bottom left
     Failure/Error: rect.bottom_left.x.should eq 1
       
       expected: 1
            got: 5
       
       (compared using ==)
     # /tmp/d20131223-4637-488e8s/spec.rb:595: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.11051 seconds
69 examples, 14 failures

Failed examples:

rspec /tmp/d20131223-4637-488e8s/spec.rb:69 # Graphics Canvas drawing of shapes and rasterization of lines works with simple horizontal lines
rspec /tmp/d20131223-4637-488e8s/spec.rb:96 # Graphics Canvas drawing of shapes and rasterization of lines works with lines with a small slope
rspec /tmp/d20131223-4637-488e8s/spec.rb:127 # Graphics Canvas drawing of shapes and rasterization of lines works with multiple lines
rspec /tmp/d20131223-4637-488e8s/spec.rb:141 # Graphics Canvas drawing of shapes and rasterization of lines draws lines with two equal ends as points
rspec /tmp/d20131223-4637-488e8s/spec.rb:154 # Graphics Canvas drawing of shapes and rasterization of rectangles works with simple rects
rspec /tmp/d20131223-4637-488e8s/spec.rb:167 # Graphics Canvas drawing of shapes and rasterization of rectangles works with rects defined with their bottom left and top right points
rspec /tmp/d20131223-4637-488e8s/spec.rb:180 # Graphics Canvas drawing of shapes and rasterization of rectangles works with rects with a zero height as a line
rspec /tmp/d20131223-4637-488e8s/spec.rb:191 # Graphics Canvas drawing of shapes and rasterization of rectangles works with rects with a zero width and height as a single point
rspec /tmp/d20131223-4637-488e8s/spec.rb:252 # Graphics Renderers Ascii renders simple canvases
rspec /tmp/d20131223-4637-488e8s/spec.rb:287 # Graphics Renderers Html renders simple canvases
rspec /tmp/d20131223-4637-488e8s/spec.rb:536 # Graphics shapes Rectangle comparison for equality is true for rectangles defined with different diagonal corners
rspec /tmp/d20131223-4637-488e8s/spec.rb:559 # Graphics shapes Rectangle comparison for equality returns the same hash for rectangles defined with different diagonal corners
rspec /tmp/d20131223-4637-488e8s/spec.rb:581 # Graphics shapes Rectangle corners top right
rspec /tmp/d20131223-4637-488e8s/spec.rb:593 # Graphics shapes Rectangle corners bottom left

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

Никола обнови решението на 16.12.2013 13:43 (преди почти 11 години)

+module Graphics
+ class Canvas
+ attr_reader :width
+ attr_reader :height
+
+ def initialize(width, height)
+ @width = width
+ @height = height
+ @board = Array.new(width) { Array.new(height) }#TODO better name
+ end
+
+ def set_pixel(x, y)
+ @board[x][y] = :set
+ end
+
+ def pixel_at?(x, y)
+ @board[x][y] == :set
+ end
+
+ def draw(shape)
+ shape.each { |x, y| set_pixel(x, y) }
+ end
+
+ def render_as(renderer)
+ renderer.render(@board)
+ end
+ end
+
+ module Renderers#TODO fix the bad design
+ class Ascii
+ def self.render(board)#TODO DRY this
+ rendered_board = ""
+ board.each do |line, index|#TODO is there a better way for last line check?
+ rendered_board.concat(line.map(&:render_pixel).to_s)
+ rendered_board.concat("\n") unless index == board.count.pred
+ end
+ rendered_board
+ end
+
+ private
+ def self.render_pixel(pixel)#TODO better name; DRY this
+ if pixel == :set
+ '@'
+ else
+ '-'
+ end
+ end
+ end
+
+ class Html
+ PREFIX = "" #TODO insert the prefix with heredoc
+ SUFFIX = "" #TODO insert the suffix with heredoc
+
+ def self.render(board)#TODO DRY this
+ rendered_board = String.new(PREFIX)
+ board.each do |line, index|#TODO is there a better way for last line check?
+ rendered_board.concat(line.map(&:render_pixel).to_s)
+ rendered_board.concat("<br>") unless index == board.count.pred
+ end
+ rendered_board.concat(SUFFIX)
+ end
+
+ private
+ def self.render_pixel(pixel)#TODO better name; DRY this
+ if pixel == :set
+ '<b></b>'
+ else
+ '<i></i>'
+ end
+ end
+ end
+ end
+
+ class Point
+ include Enumerable
+
+ attr_reader :x
+ attr_reader :y
+
+ def initialize(x, y)
+ @x = x
+ @y = y
+ end
+
+ def eql?(other)
+ [@x, @y].eql?([other.x, other.y])
+ end
+
+ def ==(other)
+ eql?(other)
+ end
+
+ def hash
+ [@x, @y].hash
+ end
+
+ def each
+ yield [@x, @y]
+ end
+ end
+
+ class Line
+ include Enumerable
+
+ attr_reader :from
+ attr_reader :to
+
+ def initialize(first_point, second_point)
+ @from, @to = [first_point, second_point].sort_by(&:y).sort_by(&:x)
+ end
+
+ def eql?(other)
+ [@from, @to].eql?([other.from, other.to])
+ end
+
+ def ==(other)
+ eql?(other)
+ end
+
+ def hash
+ [@from, @to].hash
+ end
+
+ def each(&block)
+ bresenham.each.each(&block)#or smth like that: this needs some testing
+ end
+
+ private
+ def bresenham
+ #TODO returns array of points
+ end
+ end
+
+ class Rectangle
+ include Enumerable
+
+ attr_reader :left
+ attr_reader :right
+
+ def initialize(first_point, second_point)
+ @left, @right = [first_point, second_point].sort_by(&:y).sort_by(&:x)
+ end
+
+ def top_left
+ Point.new([left.x, right.x].min, [left.y, right.y].min)
+ end
+
+ def top_right
+ Point.new([left.x, right.x].min, [left.y, right.y].max)
+ end
+
+ def bottom_left
+ Point.new([left.x, right.x].max, [left.y, right.y].min)
+ end
+
+ def bottom_right
+ Point.new([left.x, right.x].max, [left.y, right.y].max)
+ end
+
+ def eql?(other)
+ [@left, @right].eql?([other.left, other.right])
+ end
+
+ def ==(other)
+ eql?(other)
+ end
+
+ def hash
+ [@left, @right].hash
+ end
+
+ def each(&block)
+ Line.new(top_left, top_right).each(&block)
+ Line.new(top_right, bottom_right).each(&block)
+ Line.new(bottom_right, bottom_left).each(&block)
+ Line.new(bottom_left, top_left).each(&block)
+ #or smth like that: this needs some testing
+ end
+ end
+end

Малко бележки:

  • Смяташ ли, че това е най-удачното представяне на пано? Вярвам, че има и по-добри.
  • Не виждам голям смисъл от символа :set; защо не просто true?
  • Правино усещаш, че @board не е добро име; аз бих ти предложил или @canvas (защо не), или @pixels.
  • Пробвай да рефакторираш Ascii.render така, че да ползва map + join
  • След private се оставя един празен ред
  • Ред 55 – String.new(PREFIX)? Защо? Заради мутиращите операции по-долу? В тези случаи се ползва dup; и пак – map и join
  • Ползвай синоними на методи за равенството ==
  • Ред 109 и 141 са интересни, но не съм убеден, че това е достатъчно ясно написан код; убедил съм се, че е по-добре кодът да е по-прост, отколкото по-"хитър" :) Трябва да е максимално близък до семантиката, която се цели с него, в случая някаква подредба на точки

Останалото е горе-долу прилично, като на места може малко да се DRY-не, но виждам, че е все още на ранен етап.

Мерси за feedback-а.

  • Може ли да expand-неш малко за представянето на паното? Мислех, че това е най-кратката и ясна имплементация, която покрива нужния интерфейс.
  • Семантично ми звучеше по-добре пикселите да могат да бъдат :set-нати или не-:set-нати, отколкото "вярни" или не-"вярни". Но ако изглежда weird ще го сменя.
  • Всичко в Renderers е много зле и ще бъде променено.
  • Известно време се чудех доколко е добра идея include Enumerable. Не само добавя много неща в публичния интерфейс, които не се изискват от условието, но и имам чувството, че ще трябва да се грижа допълнително за определени неща, което ще резултира в добавянето на код и няма да е много красиво. По-добре ли е да има само метод to_a (или нещо подобно), който да се ползва от draw?

Отзад-напред:

  • Аз бих се въздържал да добавям излишни методи в обектите си. Бих държал интерфейса им компактен и консистентен. Не ми звучи логично точка да може да бъде "обхождана". Бих потърсил друго решение за рисуването.
  • За :set-натите пискели ти разбирам логиката. Не бих казал, че изглежда особено странно, но бих възприел този подход със символ, ако имаше повече от две състояния (поне за това се сещам, като видя символ, а не нещо, което е истина/лъжа); логиката ми, както и на интерфейса е "да, тук има пиксел", или "не, тук няма пиксел"; вж. pixel_at?
  • И продължавайки горната мисъл, виж дали няма да ти дойде някаква друга идея за вътрешно представяне на паното; не казвам, че непременно трябва да го смениш, просто като/ако ти остане време, помисли над него; така или иначе, това е вътрешно представяне и от него не би трябвало да зависят другите части от имплементацията.

Никола обнови решението на 18.12.2013 14:08 (преди почти 11 години)

module Graphics
class Canvas
attr_reader :width
attr_reader :height
def initialize(width, height)
@width = width
@height = height
- @board = Array.new(width) { Array.new(height) }#TODO better name
+ @pixels = Hash.new(false)
end
def set_pixel(x, y)
- @board[x][y] = :set
+ @pixels[[x, y]] = true
end
def pixel_at?(x, y)
- @board[x][y] == :set
+ @pixels[[x, y]]
end
def draw(shape)
- shape.each { |x, y| set_pixel(x, y) }
+ shape.to_a.each { |x, y| set_pixel(x, y) }
end
def render_as(renderer)
- renderer.render(@board)
+ renderer.render(self)
end
end
- module Renderers#TODO fix the bad design
+ module Renderers
+ def render(canvas)#TODO refactor
+ indexes = 0.upto(canvas.width.pred).to_a.product(0.upto(canvas.height.pred).to_a)
+ pixels = indexes.map(&:reverse).map { |x, y| canvas.pixel_at?(x, y) }
+ rendered_lines = pixels.map { |pixel| render_pixel(pixel) }.each_slice(canvas.width)
+ canvas_to_string = rendered_lines.map { |line| add_new_line(line) }.join.chomp
+ prefix + canvas_to_string + suffix
+ end
+
class Ascii
- def self.render(board)#TODO DRY this
- rendered_board = ""
- board.each do |line, index|#TODO is there a better way for last line check?
- rendered_board.concat(line.map(&:render_pixel).to_s)
- rendered_board.concat("\n") unless index == board.count.pred
+ extend Renderers
+
+ class << self
+ private
+
+ def render_pixel(pixel)
+ pixel ? "@" : "-"
end
- rendered_board
- end
- private
- def self.render_pixel(pixel)#TODO better name; DRY this
- if pixel == :set
- '@'
- else
- '-'
+ def add_new_line(slice)
+ slice << "\n"
end
+
+ def prefix
+ ""
+ end
+
+ def suffix
+ ""
+ end
end
end
class Html
- PREFIX = "" #TODO insert the prefix with heredoc
- SUFFIX = "" #TODO insert the suffix with heredoc
+ extend Renderers
- def self.render(board)#TODO DRY this
- rendered_board = String.new(PREFIX)
- board.each do |line, index|#TODO is there a better way for last line check?
- rendered_board.concat(line.map(&:render_pixel).to_s)
- rendered_board.concat("<br>") unless index == board.count.pred
+ class << self
+ private
+
+ PREFIX = <<-html.gsub /^\s+|$\n/, ""#TODO experiment with .freeze
+ <!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">
+ html
+
+ SUFFIX = <<-html.gsub /^\s+|$\n/, ""#TODO experiment with .freeze
+ </div>
+ </body>
+ </html>
+ html
+
+ def render_pixel(pixel)
+ pixel ? "<b></b>" : "<i></i>"
end
- rendered_board.concat(SUFFIX)
- end
- private
- def self.render_pixel(pixel)#TODO better name; DRY this
- if pixel == :set
- '<b></b>'
- else
- '<i></i>'
+ def add_new_line(slice)
+ slice << "<br>"
end
+
+ def prefix
+ PREFIX
+ end
+
+ def suffix
+ SUFFIX
+ end
end
end
end
class Point
- include Enumerable
-
attr_reader :x
attr_reader :y
def initialize(x, y)
@x = x
@y = y
end
def eql?(other)
[@x, @y].eql?([other.x, other.y])
end
- def ==(other)
- eql?(other)
- end
-
def hash
[@x, @y].hash
end
- def each
- yield [@x, @y]
+ alias_method :==, :eql?
+
+ def to_a
+ [[@x, @y]]
end
end
class Line
- include Enumerable
-
attr_reader :from
attr_reader :to
def initialize(first_point, second_point)
- @from, @to = [first_point, second_point].sort_by(&:y).sort_by(&:x)
+ if first_point.x != second_point.x
+ @from, @to = [first_point, second_point].minmax_by(&:x)
+ else
+ @from, @to = [first_point, second_point].minmax_by(&:y)
+ end
end
def eql?(other)
[@from, @to].eql?([other.from, other.to])
end
- def ==(other)
- eql?(other)
- end
-
def hash
[@from, @to].hash
end
- def each(&block)
- bresenham.each.each(&block)#or smth like that: this needs some testing
+ alias_method :==, :eql?
+
+ def to_a
+ line_coordinates(@from.x, @from.y, @to.x, @to.y)
end
private
- def bresenham
- #TODO returns array of points
+
+ def line_coordinates(start_x, start_y, end_x, end_y)
+ delta_x = end_x - start_x
+ delta_y = end_y - start_y
+ if delta_x >= delta_y
+ bresenham(delta_x, delta_y).map { |x, y| [x + start_x, y + start_y] }
+ else
+ bresenham(delta_y, delta_x).map { |y, x| [x + start_x, y + start_y] }
+ end
end
+
+ def bresenham(x, y)#TODO rename height and n
+ height = x
+ 0.upto(x).map do |n|
+ coordinates = [n, height / (2 * x)]
+ height += 2 * y
+ coordinates
+ end
+ end
end
class Rectangle
- include Enumerable
-
attr_reader :left
attr_reader :right
def initialize(first_point, second_point)
- @left, @right = [first_point, second_point].sort_by(&:y).sort_by(&:x)
+ if first_point.x != second_point.x
+ @left, @right = [first_point, second_point].minmax_by(&:x)
+ else
+ @left, @right = [first_point, second_point].minmax_by(&:y)
+ end
end
def top_left
- Point.new([left.x, right.x].min, [left.y, right.y].min)
+ Point.new([@left.x, @right.x].min, [@left.y, @right.y].min)
end
def top_right
- Point.new([left.x, right.x].min, [left.y, right.y].max)
+ Point.new([@left.x, @right.x].min, [@left.y, @right.y].max)
end
def bottom_left
- Point.new([left.x, right.x].max, [left.y, right.y].min)
+ Point.new([@left.x, @right.x].max, [@left.y, @right.y].min)
end
def bottom_right
- Point.new([left.x, right.x].max, [left.y, right.y].max)
+ Point.new([@left.x, @right.x].max, [@left.y, @right.y].max)
end
def eql?(other)
[@left, @right].eql?([other.left, other.right])
end
- def ==(other)
- eql?(other)
- end
-
def hash
[@left, @right].hash
end
- def each(&block)
- Line.new(top_left, top_right).each(&block)
- Line.new(top_right, bottom_right).each(&block)
- Line.new(bottom_right, bottom_left).each(&block)
- Line.new(bottom_left, top_left).each(&block)
- #or smth like that: this needs some testing
+ alias_method :==, :eql?
+
+ def to_a
+ all_pixels = []
+ all_pixels.concat(Line.new(top_left, top_right).to_a)
+ all_pixels.concat(Line.new(top_right, bottom_right).to_a)
+ all_pixels.concat(Line.new(bottom_right, bottom_left).to_a)
+ all_pixels.concat(Line.new(bottom_left, top_left).to_a)
+ all_pixels.uniq
end
end
end
  • Нямаш нужда от Hash.new false, nil е окей като "лъжа".
  • Коментарът #TODO refactor не трябваше да остава, както и другите ти коментари по-надолу. Не оставяй такива неща, не изглежда добре.
  • А че методът render изглежда зле – зле е. Виж моето решение за пример как съм подходил към проблема. Предпочитам инстанции на класове, отколкото статични (класови) методи.
  • Header/footer е по-подходящо от prefix/suffix в контекста на HTML код.
  • Една алтернативна имплементация на Line#to_a:

      [
          Line.new(top_left, top_right),
          Line.new(top_right, bottom_right),
          Line.new(bottom_right, bottom_left),
          Line.new(bottom_left, top_left),
      ].map(&:to_a).flatten.uniq
    

Въпреки това, смятам, че си напипал правилната посока на дизайн, имаш добри попадения, сетил си се за Hash за вътрешно представяне на пано и кодът ти е подреден и със сравнително добро именуване. Давам ти бонус точка.

Всички от изброените неща (освен това за nil), включително и неправилно работещия алгоритъм, ги бях оправил във финалната версия на домашното, но не успях да я кача, понеже последните два дни нямах интернет. Затова ви се извиних и в email-а, не беше първоначалната ми идея да оставям глупави коментари или методи, които са стена от неразбираем код. Мерси все пак за вниманието и коментарите.