From: Jesse Merriman Date: 2007-06-10T23:28:01+09:00 Subject: [SOLUTION][QUIZ] Mexican Blanket (#127) --Boundary-00=_EpAbGnH2AvkJCc+ Content-Type: Multipart/Mixed; boundary="Boundary-00=_EpAbGnH2AvkJCc+" --Boundary-00=_EpAbGnH2AvkJCc+ Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Content-Disposition: inline My solution builds a color string long enough to run across the top of the blanket, turn, and run down the right side. This is shifted left for every row. You can visualize the blanket as being a rectangular window on the parallelogram that is created, like this for 10x5: [--------] xxxxxxxxxxxxxx xxxxxxxxxxxxxx xxxxxxxxxxxxxx xxxxxxxxxxxxxx xxxxxxxxxxxxxx [--------] Building the edge string is done mainly by my Blanket.edge method, which repeatedly chooses a random color to gradient with the previous color. Blanket.symmetrical_edge can create pattern-and-color symmetrical edges by creating a half-length edge and flipping it, or pattern-but-not-color symmetrical edges by doing this and mapping the colors to random colors. I was able to re-use some of my code from Quiz 125 (fractals) to create images with RMagick. Examples: $ ./mexican_blanket.rb 60 15 OOOOOOWOOOOOWWOOOOWWWOOOWWWWOOWWWWWOWWWWWWWRWWWWWWRRWWWWWRRR OOOOOWOOOOOWWOOOOWWWOOOWWWWOOWWWWWOWWWWWWWRWWWWWWRRWWWWWRRRW OOOOWOOOOOWWOOOOWWWOOOWWWWOOWWWWWOWWWWWWWRWWWWWWRRWWWWWRRRWW OOOWOOOOOWWOOOOWWWOOOWWWWOOWWWWWOWWWWWWWRWWWWWWRRWWWWWRRRWWW OOWOOOOOWWOOOOWWWOOOWWWWOOWWWWWOWWWWWWWRWWWWWWRRWWWWWRRRWWWW OWOOOOOWWOOOOWWWOOOWWWWOOWWWWWOWWWWWWWRWWWWWWRRWWWWWRRRWWWWR WOOOOOWWOOOOWWWOOOWWWWOOWWWWWOWWWWWWWRWWWWWWRRWWWWWRRRWWWWRR OOOOOWWOOOOWWWOOOWWWWOOWWWWWOWWWWWWWRWWWWWWRRWWWWWRRRWWWWRRR OOOOWWOOOOWWWOOOWWWWOOWWWWWOWWWWWWWRWWWWWWRRWWWWWRRRWWWWRRRR OOOWWOOOOWWWOOOWWWWOOWWWWWOWWWWWWWRWWWWWWRRWWWWWRRRWWWWRRRRW OOWWOOOOWWWOOOWWWWOOWWWWWOWWWWWWWRWWWWWWRRWWWWWRRRWWWWRRRRWW OWWOOOOWWWOOOWWWWOOWWWWWOWWWWWWWRWWWWWWRRWWWWWRRRWWWWRRRRWWW WWOOOOWWWOOOWWWWOOWWWWWOWWWWWWWRWWWWWWRRWWWWWRRRWWWWRRRRWWWR WOOOOWWWOOOWWWWOOWWWWWOWWWWWWWRWWWWWWRRWWWWWRRRWWWWRRRRWWWRR OOOOWWWOOOWWWWOOWWWWWOWWWWWWWRWWWWWWRRWWWWWRRRWWWWRRRRWWWRRR $ # Blanket displayed on screen: $ ./mexican_blanket.rb 300 400 --format display $ ./mexican_blanket.rb 20 5 --symmetric GGGGGGGGOGGGYYYRYYYY GGGGGGGOGGGYYYRYYYYY GGGGGGOGGGYYYRYYYYYY GGGGGOGGGYYYRYYYYYYY GGGGOGGGYYYRYYYYYYYY $ ./mexican_blanket.rb 20 5 --color-symmetric OOOOOROOOORRRROOOORO OOOOROOOORRRROOOOROO OOOROOOORRRROOOOROOO OOROOOORRRROOOOROOOO OROOOORRRROOOOROOOOO $ ./mexican_blanket.rb 400 600 --format png Wrote blanket to blanket_400x600.png An example of that last one can be seen here: http://www.jessemerriman.com/images/ruby_quiz/127_mexican_blanket/blanket_400x600.png The full usage statement is at the top of mexican_blanket.rb If you want to change the colors or stripe widths, its hard-coded at the top of the Blanket class, and for graphics at the top of BlanketDraw. -- Jesse Merriman jessemerriman@warpmail.net http://www.jessemerriman.com/ --Boundary-00=_EpAbGnH2AvkJCc+ Content-Type: application/x-ruby; name="blanket_draw.rb" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="blanket_draw.rb" # Ruby Quiz 127: Mexican Blanket # blanket_draw.rb require 'blanket' require 'RMagick' class BlanketDraw < Magick::Draw # Colors from http://library.thinkquest.org/05aug/01280/Images/flag.jpg # Not sure how accurate that is though.. StringToColor = { 'R' => '#fe0000', 'B' => '#0333a1', 'Y' => '#ffff49', 'O' => '#ea6520', 'G' => '#039e36', 'W' => '#ffffff' } def initialize blanket super() fill_opacity 1 stroke_width 1 draw_blanket blanket self end def draw_blanket blanket # Filling in it point by point is kinda slow. Perhaps it'd be better to # find the coords of the diagonals and call line(). blanket.each_row_with_index do |row, y| row.split(//).each_with_index do |color_char, x| fill StringToColor[color_char] point x, y end end end end --Boundary-00=_EpAbGnH2AvkJCc+ Content-Type: application/x-ruby; name="blanket_image.rb" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="blanket_image.rb" # Ruby Quiz 127: Mexican Blanket # blanket_image.rb require 'blanket' require 'blanket_draw' require 'RMagick' class BlanketImage < Magick::Image def initialize blanket draw = BlanketDraw.new blanket super(blanket.width, blanket.height) draw.draw self self end end --Boundary-00=_EpAbGnH2AvkJCc+ Content-Type: application/x-ruby; name="blanket.rb" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="blanket.rb" #!/usr/bin/env ruby # Ruby Quiz 127: Mexican Blanket # mexican_blanket.rb class Array # Pick a random element. def pick; self[rand(size)]; end end class Hash # Creates a hash from an array of pairs. # Doesn't something like this already exist? def Hash.from_pairs pairs h = {} pairs.each { |pair| h[pair.first] = pair.last } h end end class Blanket Colors = %w{R B O Y G W} StripeWidths = { :small => (1..1), :large => (5..10) } attr_reader :width, :height def initialize width, height, symmetrical = false, color_symmetrical = false @width, @height = width, height length = width + height - 1 # Build upper-and-right edge. if symmetrical e = Blanket.symmetrical_edge length, color_symmetrical else e = Blanket.edge length end # Build @rows as an array of color strings. @rows = [] (0...height).each do |shift| @rows << e[shift...width+shift] end self end def to_s; @rows.join("\n"); end def each_row; @rows.each { |r| yield(r) }; end def each_row_with_index; @rows.each_with_index { |r,i| yield(r,i) }; end # Return a random small stripe width and a random large stripe width. def Blanket.random_stripe_widths smin, smax = StripeWidths[:small].min, StripeWidths[:small].max lmin, lmax = StripeWidths[:large].min, StripeWidths[:large].max [rand(smax - smin + 1) + smin, rand(lmax - lmin + 1) + lmin] end # Return a random hash mapping colors to other colors. def Blanket.random_color_map Hash.from_pairs Colors.zip(Colors.sort_by { rand }) end # Get an edge suitable for the upper-turning-the-corner-down-the-right-side # edge of the blanket as a string of color chars. # - length: the length of the edge (for the blanket: width + height - 1) def Blanket.edge length e = '' # Continually add gradients to e until we're too long, then chop off the # excess. Maybe not the best way to do it, but it works. c1 = Colors.pick while e.length < length c2 = (Colors - [c1]).pick small_width, large_width = random_stripe_widths # Add gradient of c1 -> c2 large_width.downto(small_width+1) do |w| e += c1 * w + c2 * (large_width - w + 1) end e += c1 * small_width c1 = c2 end e[0...length] end # Like edge, but with a symmetrical pattern. # - sym_colors: Colors should also be symmetrical. def Blanket.symmetrical_edge length, sym_colors = false parts = [edge(length/2)] parts << Colors.pick if (length % 2) == 1 # random center color if sym_colors parts << parts.first.reverse else color_map = random_color_map parts << parts.first.reverse.split(//).map { |char| color_map[char] } end parts.join end end --Boundary-00=_EpAbGnH2AvkJCc+ Content-Type: application/x-ruby; name="mexican_blanket.rb" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="mexican_blanket.rb" #!/usr/bin/env ruby # Ruby Quiz 127: Mexican Blanket # mexican_blanket.rb # # Usage: mexican_blanket.rb WIDTH HEIGHT [--symmetric | --color-symmetric] # [--format FORMAT] # # WIDTH: The width of the blanket. # HEIGHT: The height of the blanket. # --symmetric: The blanket should be pattern-but-not-color-symmetric. # --color-symmetric: The blanket should be pattern-and-color-symmetric. # # Formats (default: text): # text: Print out a textual representation of the blanket. # display: Use RMagick to display the image. # IMG_FORMAT: An image format like png, jpg, or gif that RMagick understands. require 'blanket' if __FILE__ == $0 width, height = ARGV[0].to_i, ARGV[1].to_i if ARGV.include? '--symmetric' blanket = Blanket.new(width, height, true, false) elsif ARGV.include? '--color-symmetric' blanket = Blanket.new(width, height, true, true) else blanket = Blanket.new(width, height) end i = ARGV.index '--format' format = (i.nil? ? 'text' : ARGV[i+1]) if format == 'text' puts blanket else require 'blanket_image' image = BlanketImage.new blanket if format == 'display' image.display else begin filename = "blanket_#{width}x#{height}.#{format}" image.write filename puts "Wrote blanket to #{filename}" rescue Exception => ex $stderr.puts "There was an error writing the image to #{filename}" $stderr.puts "Are you sure the format '#{format}' is valid?" $stderr.puts "(#{ex})" end end end end --Boundary-00=_EpAbGnH2AvkJCc+-- --Boundary-00=_EpAbGnH2AvkJCc+--