Created
October 7, 2020 23:15
-
-
Save 0xpizza/35864bcec8a6f61d947ddf148af07c8a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!python3.8 | |
| import math | |
| import random | |
| import secrets | |
| import itertools | |
| import threading | |
| import functools | |
| from types import SimpleNamespace | |
| from tkinter import * | |
| from tkinter.scrolledtext import ScrolledText | |
| def gen_rainbow(domain=None): | |
| """This generator uses the sine function to derive a | |
| cyclic sequence of numbers between 0 and 1. These numbers | |
| are used as percentages of 0xFF -- the maximum value | |
| of an 8-bit RGB code. By default, this generator yields 50 | |
| RGB codes, but can yeild any number depending on the interval | |
| of the domain. | |
| """ | |
| if domain is None: | |
| domain = itertools.cycle(x/50 for x in range(50)) # interval = 0.2 | |
| for _ in range(secrets.randbelow(50)): | |
| next(domain) # advance ? cycles for a random start color | |
| while True: | |
| x = next(domain) | |
| RGB = [ | |
| math.sin(x * 2 * math.pi) / 2 + 0.5, | |
| math.sin(x * 2 * math.pi + 2 * math.pi/3) / 2 + 0.5, | |
| math.sin(x * 2 * math.pi + 4 * math.pi / 3) / 2 + 0.5, | |
| ] | |
| yield '#' + bytes(int(y*255) for y in RGB).hex() | |
| class Die(SimpleNamespace): | |
| @classmethod | |
| def roll(cls, nsides=6, **kwargs): | |
| """Returns a Die object with its value, the number | |
| of sides the die had, and any metadata kwargs supplied | |
| by the caller of the method | |
| """ | |
| result = secrets.randbelow(nsides)+1 | |
| return cls(value=result, range=range(1, nsides+1), **kwargs) | |
| def __str__(self): | |
| return str(self.value) + ('!' if self.value == self.max else '') | |
| @property | |
| def min(self): | |
| return self.range[0] | |
| @property | |
| def max(self): | |
| return self.range[-1] | |
| class ReadOnlyText(ScrolledText): | |
| """Special scrolled text class that can only be | |
| modifed programatically and rejects user input. | |
| """ | |
| def __init__(self, master=None, **kwargs): | |
| super().__init__(master, **kwargs) | |
| self.config(state=DISABLED) | |
| self.insert = self._unlock(super().insert) | |
| self.delete = self._unlock(super().delete) | |
| def _unlock(self, f): | |
| @functools.wraps(f) | |
| def wrapped(*args, **kwargs): | |
| nonlocal self | |
| self.config(state=NORMAL) | |
| ret = f(*args, **kwargs) | |
| self.config(state=DISABLED) | |
| return ret | |
| return wrapped | |
| class BlackFrame(Frame): | |
| def __init__(self, master=None, **kwargs): | |
| super().__init__(master, **kwargs) | |
| self.configure(bg='black') | |
| class RollingDie(Frame): | |
| """Show a buncha random numbers, then slowly | |
| approach the die number, then changes from | |
| white to the die.color property. Very shiny. | |
| """ | |
| die_font = 'courier 40 bold' | |
| longest_delay = 200 #ms | |
| def __init__(self, master=None, **kwargs): | |
| self.die = kwargs.pop('die') | |
| super().__init__(master, **kwargs) | |
| # to change the die's border color we change the color of self | |
| self.config(height=30, width=30, background='white') | |
| self.total_bounces = random.randint(20,30) | |
| self.bounce_count = 0 | |
| # this is the number's black background | |
| self.inner_frame = BlackFrame(self) | |
| self.inner_frame.pack(padx=1, pady=1, fill=BOTH, expand=YES) | |
| self.die_label = Label( | |
| self.inner_frame, text='', foreground='white', | |
| background='black', font=self.die_font, | |
| ) | |
| self.die_label.pack(anchor=CENTER) | |
| self.advance_state() | |
| def advance_state(self): | |
| if self.bounce_count < self.total_bounces: | |
| self.die_label.configure(text=random.randint(self.die.min, self.die.max)) | |
| self.bounce_count += random.randint(1,3) | |
| delay = int(self.bounce_count / self.total_bounces * self.longest_delay) | |
| self.after(delay, self.advance_state) | |
| else: | |
| self.configure(background=self.die.color) | |
| self.die_label.config( | |
| text=str(self.die.value), foreground=self.die.color | |
| ) | |
| class DiceRoller(ReadOnlyText): | |
| """Scrolled textbox filled with animated labels""" | |
| def __init__(self, master=None, **kwargs): | |
| self.nsides = kwargs.pop('sidevar', None) # IntVar | |
| super().__init__(master, **kwargs) | |
| self.hide_vbar() | |
| self.configure(bg='black', borderwidth=0) | |
| # when there is no sidevar, make a default var | |
| if self.nsides is None: | |
| self.nsides = IntVar(self) | |
| self.nsides.set(6) | |
| self.color = gen_rainbow() | |
| self.bind('<Return>', self.roll) | |
| self.bind('<space>', self.roll) | |
| self.focus_set() | |
| def roll(self, _event=None): | |
| """First check to see if we used this color already. | |
| If not, then create a new tag. Start the roll animation. | |
| """ | |
| die = Die.roll(nsides=self.nsides.get(), color=next(self.color)) | |
| self.window_create(INSERT, window=RollingDie(die=die), padx=5, pady=5) | |
| self.see(INSERT) | |
| def hide_vbar(self): | |
| self.vbar.pack_forget() | |
| def show_vbar(self): | |
| self.vbar.pack(side=LEFT, fill=BOTH, expand=True) | |
| class ScrollableSpinbox(Spinbox): | |
| """https://stackoverflow.com/questions/38329996/enable-mouse-wheel-in-spinbox-tk-python | |
| """ | |
| def __init__(self, *args, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| self.bind('<MouseWheel>', self.mouseWheel) | |
| self.bind('<Button-4>', self.mouseWheel) | |
| self.bind('<Button-5>', self.mouseWheel) | |
| def mouseWheel(self, event): | |
| if event.num == 5 or event.delta == -120: | |
| self.invoke('buttondown') | |
| elif event.num == 4 or event.delta == 120: | |
| self.invoke('buttonup') | |
| class App(BlackFrame): | |
| def __init__(self, master=None, **kwargs): | |
| super().__init__(master, **kwargs) | |
| self.sidevar = IntVar(self) | |
| self.sidevar.set(6) | |
| # create spinbox for controlling die sides | |
| BlackFrame(self).pack(fill=X) # Horizontal spacer | |
| f = BlackFrame(self) | |
| f.pack(pady=10) | |
| Label( | |
| f, text='Sides:', foreground='white', background='black' | |
| ).pack(padx=5, side=LEFT, anchor=CENTER) | |
| s = ScrollableSpinbox( | |
| f, textvariable=self.sidevar, from_=1, to=99999999, | |
| background='black', foreground='white', | |
| ) | |
| s.pack(side=LEFT) | |
| BlackFrame(self).pack(fill=X) # Horizontal spacer | |
| # dice screen | |
| f = Frame(self, background='white') | |
| f.pack(expand=YES, fill=BOTH, padx=10, pady=10) | |
| DiceRoller(f, borderwidth=0, sidevar=self.sidevar)\ | |
| .pack(expand=YES, fill=BOTH, padx=1, pady=1) | |
| class Root(Tk): | |
| def __init__(self, master=None, **kwargs): | |
| super().__init__(master, **kwargs) | |
| self.title('2D Dice') | |
| App(self).pack(expand=YES, fill=BOTH) | |
| def main(): | |
| Root().mainloop() | |
| if __name__ == '__main__': | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment