else/SinWave/sinwave.py
2016-05-21 12:51:36 -07:00

270 lines
No EOL
9.6 KiB
Python

import math
import random
import string
import threading
import time
import tkinter
# Nice patterns
# 0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270, 285, 300, 315, 330, 345
# 0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330
# 0, 15, 30, 45, 60, 180, 195, 210, 225, 240
# 0, 90, 180, 270
# ░▒▓
SCREEN_WIDTH = 114
DEFAULT_LINE = {
'character': '#',
'degree': 0,
'degree_step': 1,
'horizontal_offset': 0,
'line_thickness': 1,
'original_degree': 0,
'damping': 3,
'depth': 0,
}
variables = {
'clock': 0,
'frames':[],
'delay': 0.01,
'lines':[
]
}
class LineControl(tkinter.Frame):
def __init__(self, master, line, *args, **kwargs):
tkinter.Frame.__init__(self, master, *args, **kwargs)
self.line = line
for x in range(4):
self.columnconfigure(x, minsize=40)
row = 0
tkinter.Button(self, text='\u21bb', command=self.refresh).grid(row=row, column=0, sticky='news')
tkinter.Button(self, text='Clone', command=self.clone).grid(row=row, column=1, columnspan=2, sticky='news')
tkinter.Button(self, text='X', command=self.suicide).grid(row=row, column=3, sticky='news')
row += 1
tkinter.Label(self, text='Degree:').grid(row=row, column=0, sticky='e')
self.entry_degree = tkinter.Entry(self, width=3)
self.entry_degree.insert(0, self.line['original_degree'])
self.entry_degree.grid(row=row, column=1, sticky='news')
tkinter.Button(self, text='Set', command=self.apply_degree).grid(row=row, column=2, sticky='news')
row += 1
def create_buttonpair(key, row):
minus = tkinter.Button(self, text='-', command=lambda *a: self.step_variable(key, -1, self.value_labels[key]))
minus.grid(row=row, column=1, sticky='news')
plus = tkinter.Button(self, text='+', command=lambda *a: self.step_variable(key, +1, self.value_labels[key]))
plus.grid(row=row, column=2, sticky='news')
self.value_labels = {}
tkinter.Label(self, text='Step:').grid(row=row, column=0, sticky='e')
create_buttonpair('degree_step', row)
self.value_labels['degree_step'] = tkinter.Label(self, text=str(self.line['degree_step']))
self.value_labels['degree_step'].grid(row=row, column=3)
row += 1
tkinter.Label(self, text='Thick:').grid(row=row, column=0, sticky='e')
create_buttonpair('line_thickness', row)
self.value_labels['line_thickness'] = tkinter.Label(self, text=str(self.line['line_thickness']))
self.value_labels['line_thickness'].grid(row=row, column=3)
row += 1
tkinter.Label(self, text='Offset:').grid(row=row, column=0, sticky='e')
create_buttonpair('horizontal_offset', row)
self.value_labels['horizontal_offset'] = tkinter.Label(self, text=str(self.line['horizontal_offset']))
self.value_labels['horizontal_offset'].grid(row=row, column=3)
row += 1
tkinter.Label(self, text='Dampen:').grid(row=row, column=0, sticky='e')
create_buttonpair('damping', row)
self.value_labels['damping'] = tkinter.Label(self, text=str(self.line['damping']))
self.value_labels['damping'].grid(row=row, column=3)
row += 1
tkinter.Label(self, text='Depth:').grid(row=row, column=0, sticky='e')
create_buttonpair('depth', row)
self.value_labels['depth'] = tkinter.Label(self, text=str(self.line['depth']))
self.value_labels['depth'].grid(row=row, column=3)
row += 1
tkinter.Label(self, text='Char:').grid(row=row, column=0, sticky='e')
self.entry_character = tkinter.Entry(self, width=2, font=('Consolas', 12))
self.entry_character.grid(row=row, column=1, sticky='news')
self.entry_character.insert(0, self.line['character'])
tkinter.Button(self, text='Set', command=self.apply_character).grid(row=row, column=2, sticky='news')
row += 1
def apply_character(self, *trash):
char = self.entry_character.get()
try:
assert len(char) == 1
except:
return
self.line['character'] = char
def apply_degree(self, *trash):
value = self.entry_degree.get()
try:
value = int(value)
except ValueError:
return
self.line['original_degree'] = value % 360
self.refresh()
regrid_frames()
def clone(self, *trash):
line = self.line.copy()
create_line_frame(line=line)
regrid_frames()
def refresh(self, *trash):
self.line['degree'] = self.line['original_degree'] + (self.line['degree_step'] * variables['clock'])
def step_variable(self, key, direction, label):
value = self.line[key]
value += direction
if key == 'line_thickness':
value = max(value, 0)
elif key == 'horizontal_offset':
value = value % SCREEN_WIDTH
elif key == 'delay':
value = max(value, 0.001)
elif key in ('damping', 'depth'):
value = max(value, 0)
value = min(value, int(SCREEN_WIDTH / 2))
self.line[key] = value
label.configure(text=str(value)[:5])
def suicide(self, *trash):
unregister_line(self.line)
variables['frames'].remove(self)
self.grid_forget()
regrid_frames()
def create_line(degree_offset, **kwargs):
line = DEFAULT_LINE.copy()
line.update(kwargs)
line['original_degree'] = degree_offset
line['degree'] = (variables['clock'] + degree_offset) % 360
# Line ID exists solely to make sure that even clones are unique.
line['id'] = random.getrandbits(32)
return line
def create_line_frame(offset=None, line=None):
if line is None:
if offset is None:
offset = entry_add.get()
offset = offset.replace(' ', '')
offset = offset.split(',')
try:
offset = [int(o) for o in offset]
except ValueError:
return
entry_add.delete(0, 'end')
lines = []
for x in offset:
line = create_line(x)
lines.append(line)
else:
line['id'] = random.getrandbits(32)
lines = [line]
for line in lines:
register_line(line)
frame = LineControl(t, line, relief='groove', borderwidth=2)
variables['frames'].append(frame)
regrid_frames()
def map_range(x, old_low, old_high, new_low, new_high):
'''
Given a number x in range [old_low, old_high], return corresponding
number in range [new_low, new_high].
'''
if x > old_high or x < old_low:
raise ValueError('%d not in range [%d..%d]' % (x, old_low, old_high))
percentage = (x - old_low) / (old_high - old_low)
y = (percentage * (new_high - new_low)) + new_low
return y
def position_from_degree(d, damping_units):
rad = math.radians(d)
sin = math.sin(rad)
sin = map_range(sin, -1, 1, 0, 1)
position = sin * (SCREEN_WIDTH - 1)
position = map_range(position, 0, SCREEN_WIDTH, damping_units, SCREEN_WIDTH-damping_units)
#position = (position * (1- (2 * damping_percent))) + damping_units
position = round(position)
position = position % SCREEN_WIDTH
return position
def print_loop():
while True:
next_frame = time.time() + variables['delay']
variables['clock'] = (variables['clock'] + 1) % 360
screen = ([' '] * SCREEN_WIDTH)
z_indexes = {}
for line_vars in variables['lines']:
start_degree = line_vars['degree']
end_degree = (start_degree + line_vars['degree_step']) % 360
line_vars['degree'] = end_degree
damping = line_vars['damping']
start_pos = position_from_degree(start_degree, damping)
end_pos = position_from_degree(end_degree, damping)
(start_pos, end_pos) = sorted([start_pos, end_pos])
start_pos -= line_vars['line_thickness']
end_pos += line_vars['line_thickness']
mini_step = line_vars['degree_step'] / max((end_pos - start_pos), 1)
for (index, position) in enumerate(range(start_pos, end_pos+1)):
position = (position + line_vars['horizontal_offset']) % SCREEN_WIDTH
this_rads = math.radians(start_degree + (mini_step * index))
z_index = math.cos(this_rads)
z_index = map_range(z_index, -1, 1, line_vars['depth'], SCREEN_WIDTH-line_vars['depth'])
#print(line_vars['character'], z_index)
if z_index > z_indexes.get(position, -2):
screen[position] = line_vars['character']
z_indexes[position] = z_index
print('%03d' % variables['clock'], ''.join(screen))
delay = max(next_frame - time.time(), 0)
time.sleep(delay)
def register_line(line):
variables['lines'].append(line)
def regrid_frames():
frames = variables['frames']
#frames.sort(key=lambda x: x.line['original_degree'])
for (index, frame) in enumerate(frames):
(row, column) = divmod(index, 4)
row += 1
frame.grid(row=row, column=column)
def unregister_line(line):
variables['lines'].remove(line)
t = tkinter.Tk()
frame_add = tkinter.Frame(t)
entry_add = tkinter.Entry(frame_add)
entry_add.grid(row=0, column=0)
tkinter.Button(frame_add, text='+', command=create_line_frame).grid(row=0, column=1)
frame_add.grid(row=0, column=0)
frame_delay = tkinter.Frame(t)
tkinter.Label(frame_delay, text='Speed:')
thread = threading.Thread(target=print_loop)
thread.daemon=True
thread.start()
create_line_frame([0])
t.mainloop()