#!/usr/bin/ruby -w # Generate input text for testing rotation angles. # # The goal is to generate something that will retain all character # locations after a single rotation step, such that after 26 rotation # steps we get back the original input. # Rotation angle. ANGLE = Math::PI / 13 # Radii where output characters will be inserted. Only some subset of radii # are stable. RADII = [8, 14, 16, 20] # List of (x, y) pairs. points = [] RADII.each{|x| points += [[x, 0]] } # Collect rotated points into list of (y, x, character) tuples. output_points = [] occupied_points = {} rx = Math::tan(ANGLE / 2) ry = Math::sin(ANGLE) 26.times{|i| # Add unrotated points. points.size.times{|j| x, y = points[j] if occupied_points[[x, y]] raise "Overlapping point at (#{x}, #{y})\n" end c = j == 0 ? ('a'.ord + (26 - i) % 26).chr : ('A'.ord + i).chr output_points += [[y, x, c]] occupied_points[[x, y]] = true } # Apply rotation. points.map!{|t| x, y = t x -= (rx * y).round y += (ry * x).round x -= (rx * y).round [x, y] } } # Add center point. output_points += [[0, 0, '+']] # Output final set of points. min_x = min_y = 0 output_points.each{|t| y, x = t min_x = [min_x, x].min min_y = [min_y, y].min } cursor_x = min_x cursor_y = min_y text = "" output_points.sort.each{|t| y, x, c = t if cursor_y < y text += "\n" * (y - cursor_y) cursor_y = y cursor_x = min_x end text += " " * (x - cursor_x) + c cursor_x = x + 1 } puts text