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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
|
import sys
from py.io import ansi_print, get_terminal_width
"""
Black 0;30 Dark Gray 1;30
Blue 0;34 Light Blue 1;34
Green 0;32 Light Green 1;32
Cyan 0;36 Light Cyan 1;36
Red 0;31 Light Red 1;31
Purple 0;35 Light Purple 1;35
Brown 0;33 Yellow 1;33
Light Gray 0;37 White 1;37
"""
import os
if os.environ.get('TERM', 'dumb').find('256') > 0:
from ansiramp import ansi_ramp80
palette = map(lambda x: "38;5;%d" % x, ansi_ramp80)
else:
palette = [39, 34, 35, 36, 31, 33, 32, 37]
# used for debugging/finding new coordinates
# How to:
# 1. Set DEBUG to True
# 2. Add a new coordinate to coordinates with a high distance and high max colour (e.g. 300)
# 3. Run, pick an interesting coordinate from the shown list and replace the newly added
# coordinate by it.
# 4. Rerun to see the max colour, insert this max colour where you put the high max colour.
# 5. Set DEBUG to False
DEBUG = False
class Mandelbrot:
def __init__ (self, width=100, height=28, x_pos=-0.5, y_pos=0, distance=6.75):
self.xpos = x_pos
self.ypos = y_pos
aspect_ratio = 1/3.
factor = float(distance) / width # lowering the distance will zoom in
self.xscale = factor * aspect_ratio
self.yscale = factor
self.iterations = 170
self.x = width
self.y = height
self.z0 = complex(0, 0)
def init(self):
self.reset_lines = False
xmin = self.xpos - self.xscale * self.x / 2
ymin = self.ypos - self.yscale * self.y / 2
self.x_range = [xmin + self.xscale * ix for ix in range(self.x)]
self.y_range = [ymin + self.yscale * iy for iy in range(self.y)]
def reset(self, cnt):
self.reset_lines = cnt
def generate(self):
self.reset_lines = False
iy = 0
while iy < self.y:
ix = 0
while ix < self.x:
c = complex(self.x_range[ix], self.y_range[iy])
z = self.z0
colour = 0
mind = 2
for i in range(self.iterations):
z = z * z + c
d = abs(z)
if d >= 2:
colour = min(int(mind / 0.007), 254) + 1
break
else:
mind = min(d, mind)
yield ix, iy, colour
if self.reset_lines is not False: # jump to the beginning of the line
iy += self.reset_lines
do_break = bool(self.reset_lines)
self.reset_lines = False
if do_break:
break
ix = 0
else:
ix += 1
iy += 1
class Driver(object):
zoom_locations = [
# x, y, "distance", max color range
(0.37865401, 0.669227668, 0.04, 111),
(-1.2693, -0.4145, 0.2, 105),
(-1.2693, -0.4145, 0.05, 97),
(-1.2642, -0.4185, 0.01, 95),
(-1.15, -0.28, 0.9, 94),
(-1.15, -0.28, 0.3, 58),
(-1.15, -0.28, 0.05, 26),
]
def __init__(self, **kwargs):
self.kwargs = kwargs
self.zoom_location = -1
self.max_colour = 256
self.colour_range = None
self.invert = True
self.interesting_coordinates = []
self.init()
def init(self):
self.width = get_terminal_width() or 80 # in some envs, the py lib doesnt default the width correctly
self.mandelbrot = Mandelbrot(width=(self.width or 1), **self.kwargs)
self.mandelbrot.init()
self.gen = self.mandelbrot.generate()
def reset(self, cnt=0):
""" Resets to the beginning of the line and drops cnt lines internally. """
self.mandelbrot.reset(cnt)
def restart(self):
""" Restarts the current generator. """
print >>sys.stderr
self.init()
def dot(self):
""" Emits a colourful character. """
x = c = 0
try:
x, y, c = self.gen.next()
if x == 0:
width = get_terminal_width()
if width != self.width:
self.init()
except StopIteration:
if DEBUG and self.interesting_coordinates:
print >>sys.stderr, "Interesting coordinates:", self.interesting_coordinates
self.interesting_coordinates = []
kwargs = self.kwargs
self.zoom_location += 1
self.zoom_location %= len(self.zoom_locations)
loc = self.zoom_locations[self.zoom_location]
kwargs.update({"x_pos": loc[0], "y_pos": loc[1], "distance": loc[2]})
self.max_colour = loc[3]
if DEBUG:
# Only used for debugging new locations:
print "Colour range", self.colour_range
self.colour_range = None
self.restart()
return
if self.print_pixel(c, self.invert):
self.interesting_coordinates.append(dict(x=(x, self.mandelbrot.x_range[x]),
y=(y, self.mandelbrot.y_range[y])))
if x == self.width - 1:
print >>sys.stderr
def print_pixel(self, colour, invert=1):
chars = [".", ".", "+", "*", "%", "#"]
idx = lambda chars: (colour+1) * (len(chars) - 1) / self.max_colour
if invert:
idx = lambda chars, idx=idx:len(chars) - 1 - idx(chars)
char = chars[idx(chars)]
ansi_colour = palette[idx(palette)]
ansi_print(char, ansi_colour, newline=False, flush=True)
if DEBUG:
if self.colour_range is None:
self.colour_range = [colour, colour]
else:
old_colour_range = self.colour_range
self.colour_range = [min(self.colour_range[0], colour), max(self.colour_range[1], colour)]
if old_colour_range[0] - colour > 3 or colour - old_colour_range[1] > 3:
return True
if __name__ == '__main__':
import random
from time import sleep
d = Driver()
while True:
sleep(random.random() / 800)
d.dot()
if 0 and random.random() < 0.01:
string = "WARNING! " * 3
d.jump(len(string))
print string,
|