1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
|
require "reline"
module IRB
class << self
class Vec
def initialize(x, y, z)
@x, @y, @z = x, y, z
end
attr_reader :x, :y, :z
def sub(other)
Vec.new(@x - other.x, @y - other.y, @z - other.z)
end
def dot(other)
@x*other.x + @y*other.y + @z*other.z
end
def cross(other)
ox, oy, oz = other.x, other.y, other.z
Vec.new(@y*oz-@z*oy, @z*ox-@x*oz, @x*oy-@y*ox)
end
def normalize
r = Math.sqrt(self.dot(self))
Vec.new(@x / r, @y / r, @z / r)
end
end
class Canvas
def initialize((h, w))
@data = (0..h-2).map { [0] * w }
@scale = [w / 2.0, h-2].min
@center = Complex(w / 2, h-2)
end
def line((x1, y1), (x2, y2))
p1 = Complex(x1, y1) / 2 * @scale + @center
p2 = Complex(x2, y2) / 2 * @scale + @center
line0(p1, p2)
end
private def line0(p1, p2)
mid = (p1 + p2) / 2
if (p1 - p2).abs < 1
x, y = mid.rect
@data[y / 2][x] |= (y % 2 > 1 ? 2 : 1)
else
line0(p1, mid)
line0(p2, mid)
end
end
def draw
@data.each {|row| row.fill(0) }
yield
@data.map {|row| row.map {|n| " ',;"[n] }.join }.join("\n")
end
end
class RubyModel
def initialize
@faces = init_ruby_model
end
def init_ruby_model
cap_vertices = (0..5).map {|i| Vec.new(*Complex.polar(1, i * Math::PI / 3).rect, 1) }
middle_vertices = (0..5).map {|i| Vec.new(*Complex.polar(2, (i + 0.5) * Math::PI / 3).rect, 0) }
bottom_vertex = Vec.new(0, 0, -2)
faces = [cap_vertices]
6.times do |j|
i = j-1
faces << [cap_vertices[i], middle_vertices[i], cap_vertices[j]]
faces << [cap_vertices[j], middle_vertices[i], middle_vertices[j]]
faces << [middle_vertices[i], bottom_vertex, middle_vertices[j]]
end
faces
end
def render_frame(i)
angle = i / 10.0
dir = Vec.new(*Complex.polar(1, angle).rect, Math.sin(angle)).normalize
dir2 = Vec.new(*Complex.polar(1, angle - Math::PI/2).rect, 0)
up = dir.cross(dir2)
nm = dir.cross(up)
@faces.each do |vertices|
v0, v1, v2, = vertices
if v1.sub(v0).cross(v2.sub(v0)).dot(dir) > 0
points = vertices.map {|p| [nm.dot(p), up.dot(p)] }
(points + [points[0]]).each_cons(2) do |p1, p2|
yield p1, p2
end
end
end
end
end
private def easter_egg_logo(type)
@easter_egg_logos ||= File.read(File.join(__dir__, 'ruby_logo.aa'), encoding: 'UTF-8:UTF-8')
.split(/TYPE: ([A-Z_]+)\n/)[1..]
.each_slice(2)
.to_h
@easter_egg_logos[type.to_s.upcase]
end
private def easter_egg(type = nil)
print "\e[?1049h"
type ||= [:logo, :dancing].sample
case type
when :logo
Pager.page do |io|
logo_type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode_large : :ascii_large
io.write easter_egg_logo(logo_type)
STDIN.raw { STDIN.getc } if io == STDOUT
end
when :dancing
STDOUT.cooked do
interrupted = false
prev_trap = trap("SIGINT") { interrupted = true }
canvas = Canvas.new(Reline.get_screen_size)
Reline::IOGate.set_winch_handler do
canvas = Canvas.new(Reline.get_screen_size)
end
ruby_model = RubyModel.new
print "\e[?25l" # hide cursor
0.step do |i| # TODO (0..).each needs Ruby 2.6 or later
buff = canvas.draw do
ruby_model.render_frame(i) do |p1, p2|
canvas.line(p1, p2)
end
end
buff[0, 20] = "\e[0mPress Ctrl+C to stop\e[31m\e[1m"
print "\e[H" + buff
sleep 0.05
break if interrupted
end
rescue Interrupt
ensure
print "\e[?25h" # show cursor
trap("SIGINT", prev_trap)
end
end
ensure
print "\e[0m\e[?1049l"
end
end
end
IRB.__send__(:easter_egg, ARGV[0]&.to_sym) if $0 == __FILE__
|