[
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n"
  },
  {
    "path": ".gitignore",
    "content": "**/__pycache__\nmedia/*.wav\nmedia/*.WAV\nmedia/*.mp3\nmedia/*.MP3\nvenv"
  },
  {
    "path": "CLIRender/classes.py",
    "content": "import os\nfrom platform import system as system_type\nfrom bisect import insort_right, bisect_right\n\nfrom CLIRender.dat import Vector2\n\n\nclass RenderSection:\n    id_inc = 0\n\n    def __init__(self, string, start, code):\n        self.id = RenderSection.id_inc\n        RenderSection.id_inc += 1\n\n        self.string = string\n        self.start = start\n        self.length = len(self.string)\n        self.end = self.start + self.length\n        self.code = code\n\n    def __lt__(self, other):\n        return self.start < other\n\n    def __gt__(self, other):\n        return self.start > other\n\n    def add_char(self, char):\n        self.string += char\n        self.end += 2\n        self.length += 2\n\n    def mod_char(self, char, loc):\n        if loc == self.end:\n            self.add_char(char)\n        else:\n            self.string = self.string[:loc] + char + self.string[loc + 2:]\n\n    # The addition operation adds the other section to this section. The other section takes precedence.\n    # This only works with text and discards the other section's code.\n    def add_section(self, other):\n        diff = other.start - self.start\n        self.string = self.string[:diff] + other.string + self.string[diff + other.length:]\n        self.length = len(self.string)\n        self.start = min(other.start, self.start)\n        self.end = self.start + self.length\n\n    # The addition operation adds the other section to this section. The self section takes precedence.\n    # This only works with text and discards the other section's code.\n    def add_section_below(self, other):\n        diff = self.start - other.start\n        self.string = other.string[:diff] + self.string + other.string[diff + self.length:]\n        self.length = len(self.string)\n        self.start = min(other.start, self.start)\n        self.end = self.start + self.length\n\n    # Same as section subtraction but only one char.\n    def subtract_char(self, loc):\n        first = None\n        last = None\n        if loc > self.start:\n            diff = loc - self.start\n            first = RenderSection(self.string[:diff], self.start, self.code)\n\n        if loc < self.end:\n            diff = self.end - loc\n            last = RenderSection(self.string[diff:], loc + 2, self.code)\n\n        return first, last\n\n    # Subtracts another section's length from this section. This returns a 2-tuple containing the two new subsections.\n    def subtract_section(self, other):\n        first = None\n        last = None\n        if other.start > self.start:\n            # if the other string starts later, there's still a part of the victim string remaining\n            diff = other.start - self.start\n            first = RenderSection(self.string[:diff], self.start, self.code)\n\n        if other.end < self.end:\n            # if the other string ends before us, the latter part needs something too\n            diff = self.end - other.end\n            last = RenderSection(self.string[self.length - diff:], other.end, self.code)\n\n        return first, last\n\n\nclass Canvas:\n    # TODO write multi-layer support. shouldn't be too hard, just merge down layers as we need\n    # but not right now. plaao tired\n\n    def __init__(self, dimensions, num_layers, merge_rules):\n        self.dimensions = Vector2(dimensions.x * 2, dimensions.y)\n        self.num_layers = num_layers\n        self.layers = [Layer(i, self.dimensions) for i in range(num_layers)]\n        self.merge_rules = merge_rules\n        self.edits_this_frame = 0\n\n    def set_char(self, layer, location, char, code):\n        self.layers[layer].set_char(int(location.x * 2) + (location.y * self.dimensions.x), char, code)\n        self.edits_this_frame += 1\n\n    def set_string(self, layer, location, string, code):\n        self.layers[layer].set_string(int(location.x * 2) + (location.y * self.dimensions.x), string, code)\n        self.edits_this_frame += 1\n\n    def clear_layer(self, layer):\n        self.layers[layer].set_string(0, \" \" * self.dimensions.x * self.dimensions.y, \"\")\n\n    def render_blank(self):\n        print(\"\\033[1;1H\\033[1;37;40m\" + ((\"  \" * self.dimensions.x + \"\\n\") * self.dimensions.y))\n\n    def render_all(self):\n        # Merge layers from bottom to top then render the resulting group list.\n        # Here is the only point we even think about line lengths.\n        total_string = \"\\033[1;1H\\033[1;39m\"\n        for group in self.layers[0].new_groups:\n            lineno = group.start // self.dimensions.x\n            add_string = \"\"\n            add_string += \"\\033[{};{}H\".format(\n                lineno + 1, group.start % self.dimensions.x + 1\n            )\n\n            add_string += group.code\n\n            offset = group.start % self.dimensions.x\n            # If overrun is greater than x, then it breaks a line boundary.\n            # So, for every x overrun, we add a \\n between lines.\n            split = 0\n            stop = 0\n            while stop <= group.length:\n                if lineno >= self.dimensions.y:\n                    break\n\n                stop = self.dimensions.x - offset + split\n                add_string += group.string[split:stop] + (\"\\n\" if stop <= group.length else \"\")\n                offset = 0\n                lineno += 1\n                split += stop - split\n\n            # We're done! How simple ...\n            total_string += add_string\n\n        self.layers[0].new_groups.clear()\n        self.edits_this_frame = 0\n        print(total_string + \"\\033[27;0H\", end=\"\\n\")\n\n\nclass Layer:\n    def __init__(self, layer_id, dimensions):\n        self.dimensions = Vector2(dimensions.x * 2, dimensions.y)\n        self.max_loc = self.dimensions.x * self.dimensions.y\n\n        self.id = layer_id\n        # new_groups contains a list of groups of text that need to be rendered next time this layer is flattened.\n        self.new_groups = []\n        self.full_str = [\"\" for i in range(self.dimensions.x * self.dimensions.y)]\n\n    def set_char(self, loc, char, code):\n        # Set a single char in the layer.\n        # Step 1: binary search through new_groups to see if we need to make a new group\n        # Step 2: make a new group or mod the found group\n        #\n        # We want to find the rightmost value less than or equal to loc,\n        # then check if it encompasses the given position.\n        i = bisect_right(self.new_groups, loc) - 1\n        if i >= len(self.new_groups) or i < 0:\n            i = None\n        else:\n            # Also check backwards for things with the SAME start location.\n            while i >= 1 and self.new_groups[i - 1].start == loc:\n                i -= 1\n\n        if i is not None:\n            # If a group already exists, we might need to split it.\n            # This happens when codes are not equal.\n            # In that situation, split our insert group in two and add a new one as normal.\n            insert_group = self.new_groups[i]\n\n            # This is only ever relevant when our found group actually reaches loc.\n            if insert_group.end >= loc:\n                if insert_group.code != code:\n                    sub_groups = insert_group.subtract_char(loc)\n                    self.new_groups.pop(i)\n\n                    if sub_groups[0]:\n                        insort_right(self.new_groups, sub_groups[0])\n                    if sub_groups[1]:\n                        insort_right(self.new_groups, sub_groups[1])\n\n                    i = None\n                else:\n                    insert_group.mod_char(char, loc)\n            else:\n                i = None\n\n        if i is None:\n            # Add this char as a new group.\n            insort_right(self.new_groups, RenderSection(char, loc, code))\n\n        # Then replace the full string at location with our new thing.\n        # This is only as a contingency if our console crashes / burns / explodes / vanishes without a trace.\n        # It should be disableable if necessary.\n\n        # print(\"\\n\".join(\"\".join(char for char in canvas.layers[0].full_str[line * 40:(line + 1) * 40]) for line in range(10)))\n        self.full_str[loc] = code + char\n\n    def has_duplicate_starts(self):\n        non_dupe_list = []\n        for group in self.new_groups:\n            if group in non_dupe_list:\n                return False\n\n            non_dupe_list.append(group)\n\n        return True\n\n    def set_string(self, loc, string, code):\n        # Find the two groups that are closest to the start and end points of the group.\n        # Perform the add function on both if they exist, or the sub function if their code is different.\n        # Remove all groups in between.\n        # Find the rightmost section with start less than or equal to loc; so, the closest element that can intersect.\n        # IT MAY NOT INTERSECT. CHECK. IF IT DOESN'T, THEN NOTHING INTERSECTS START.\n        # If we are out of bounds, leave instantly\n        if loc >= self.max_loc or loc < 0:\n            return\n\n        start_i = bisect_right(self.new_groups, loc) - 1\n        if start_i < 0:\n            start_i = 0\n\n        # Also check backwards for things with the SAME start location.\n        while start_i >= 1 and self.new_groups[start_i - 1].start == loc:\n            start_i -= 1\n\n        # Cut the string down to the maximum possible length it could be.\n        cut_string = string[:self.max_loc - loc]\n        add_group = RenderSection(cut_string, loc, code)\n        add_groups = [None, add_group, None]\n        remove = []\n\n        for mid_i in range(start_i, len(self.new_groups)):\n            # If the group is the same, add. If it's different, subtract.\n            # Fill up add_groups with subtracted items.\n            mid_group = self.new_groups[mid_i]\n\n            # If it starts after our end, break. We're out.\n            if mid_group.start >= add_group.end:\n                break\n\n            # If it ends before our start, discard. We don't need it.\n            if mid_group.end > add_group.start:\n                # If it is eclipsed, we don't need it at all.\n                if mid_group.start >= add_group.start and mid_group.end <= add_group.end:\n                    remove.append(mid_i)\n                else:\n                    # Otherwise, run either subtract or add on it.\n                    if mid_group.code == add_group.code:\n                        # Add the mid group to the code. Delete the mid group.\n                        add_group.add_section_below(mid_group)\n                        remove.append(mid_i)\n                    else:\n                        # Subtract the add group from the mid group. Remove previous mid group.\n                        sub_groups = mid_group.subtract_section(add_group)\n                        if sub_groups[0]:\n                            add_groups[0] = sub_groups[0]\n                        if sub_groups[1]:\n                            add_groups[2] = sub_groups[1]\n\n                        remove.append(mid_i)\n\n        remove.reverse()\n        for index in remove:\n            self.new_groups.pop(index)\n\n        # THERE'S A BETTER WAY OF DOING THIS; I JUST DONT KNOW WHAT RIGHT NOW :(\n        add_index = bisect_right(self.new_groups, loc)\n        for group in add_groups:\n            if group:\n                self.new_groups.insert(start_i + add_index, group)\n                add_index += 1\n\n        # For full string usage.\n        for index, char in enumerate(string):\n            self.full_str[index] = code + char\n\n\ndef enable_ansi():\n    \"\"\"\n    Uses a hack to enable ANSI mode in the Windows console. Does nothing on Linux.\n    \"\"\"\n    if system_type() == \"Windows\":\n        # might fuck around\n        os.system(\"\")\n\n\n\"\"\"\nenable_ansi()\ncanvas = Canvas(Vector2(40, 26), 1, ())\ncanvas.render_blank()\nimport time\nimport random\nind = 0\nfor j in range(50):\n    canvas.render_all()\n    canvas.layers[0].set_char(\n        ind % (40 * 26) * 2, \"##\", \"\\033[{}m\".format(31 + (ind % 12) // 2)\n    )\n    ind += random.randint(0, 80 * 26)\n    canvas.layers[0].set_char(\n        ind % (40 * 26) * 2, \"##\", \"\\033[{}m\".format(31 + (ind % 12) // 2)\n    )\n    ind += random.randint(0, 80 * 26)\n\n\ninput(\"\")\nind = 0\nwhile True:\n    canvas.render_all()\n    for yval in range(26):\n        if ind % 14 == 13:\n            canvas.layers[0].set_string(\n                0 + (80 * yval), \"---------|\" if ind % 2 == 0 else \"~~~~~~~~~/\", \"\\033[{}m\".format(32)\n            )\n\n            canvas.layers[0].set_string(\n                20 + (80 * yval), \"|---------\" if ind % 2 == 0 else \"\\~~~~~~~~~\", \"\\033[{}m\".format(32)\n            )\n\n            canvas.layers[0].set_string(\n                10 + (80 * yval), \"          \", \"\\033[{}m\".format(32)\n            )\n        else:\n            canvas.layers[0].set_string(\n                0 + (80 * yval), \"---------|\" if ind % 2 == 0 else \"~~~~~~~~~/\", \"\\033[{}m\".format(32)\n            )\n\n            canvas.layers[0].set_string(\n                20 + (80 * yval), \"|---------\" if ind % 2 == 0 else \"\\~~~~~~~~~\", \"\\033[{}m\".format(32)\n            )\n\n            canvas.layers[0].set_string(\n                14 - (1 * (ind % 14)) + (80 * yval), \"<{}>\".format(\"--\" * (ind % 14)), \"\\033[{}m\".format((34, 32, 33, 31)[int((ind % 14) / 14 * 4)])\n            )\n\n    # canvas.layers[0].set_string(\n    #     i % (40 * 26), \"abcdefghijklmnopqrstuvwxyz\", \"\\033[{}m\".format(31 + (i % 12) // 2)\n    # )\n    ind += 1\n\n# TODO write nicer functions and multilayer support\n# multilayer entails searching upwards for groups that intersect; if so, adding them to the layer\n# nicer functions means an actual set_char for the canvas that takes in x, y rather than absolute loc\n# also add set_string which can take in a longer string, subtracting any intersecting groups\n\"\"\""
  },
  {
    "path": "CLIRender/dat.py",
    "content": "# contains:\n#\n#    x position (generic typing because python but should be an int in most cases)\n#    y position (the same)\n#\nclass Vector2:  # to be honest this is me showing off\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\n    def __str__(self):  # returns a formatted string for the vector2\n        return \"({v.x}, {v.y})\".format(v=self)\n\n    def __add__(self, other):  # allows adding of vector2s, and ints\n        if isinstance(other, Vector2):\n            return Vector2(self.x + other.x, self.y + other.y)\n        elif isinstance(other, (int, float)):\n            return Vector2(self.x + other, self.y + other)\n        else:\n            raise ValueError(\"Accepted data types are float, int and Vector2.\")\n\n    def __mul__(self, other):  # multiplies a vector2 by an int, or by another vector2\n        if isinstance(other, Vector2):\n            return Vector2(self.x * other.x, self.y * other.y)\n        elif isinstance(other, (int, float)):\n            return Vector2(self.x * other, self.y * other)\n        else:\n            raise ValueError(\"Accepted data types are float, int and Vector2.\")\n\n    def __truediv__(self, other):  # returns a FLOAT value (true div)\n        if isinstance(other, Vector2):\n            return Vector2(self.x / other.x, self.y / other.y)\n        elif isinstance(other, (int, float)):\n            return Vector2(self.x / other, self.y / other)\n        else:\n            raise ValueError(\"Accepted data types are float, int and Vector2.\")\n\n    def __floordiv__(self, other):  # returns an INT value (floor div)\n        if isinstance(other, Vector2):\n            return Vector2(self.x // other.x, self.y // other.y)\n        elif isinstance(other, (int, float)):\n            return Vector2(self.x // other, self.y // other)\n        else:\n            raise ValueError(\"Accepted data types are float, int and Vector2.\")\n\n    def __pos__(self):\n        return Vector2(abs(self.x), abs(self.y))\n\n    def __neg__(self):\n        return Vector2(-self.x, -self.y)\n        \n    def __eq__(self, other):\n        return self.x == other.x and self.y == other.y\n    \n    def Magnitude(self):  # returns the magnitude of the vector2. (length of the vector)\n        return ((self.x ** 2) + (self.y ** 2)) ** (1/2) #sqrt\n\n\nclass Vector3:  # to be honest this is me showing off\n    def __init__(self, x, y, z):\n        self.x = x\n        self.y = y\n        self.z = z\n\n    def __str__(self):  # returns a formatted string for the Vector3\n        return \"({v.x}, {v.y}, {v.z})\".format(v=self)\n\n    def __add__(self, other):  # allows adding of Vector3s, and ints\n        if isinstance(other, Vector3):\n            return Vector3(self.x + other.x, self.y + other.y, self.z + other.z)\n        elif isinstance(other, (int, float)):\n            return Vector3(self.x + other, self.y + other, self.z + other)\n        else:\n            raise ValueError(\"Accepted data types are float, int and Vector3.\")\n\n    def __mul__(self, other):  # multiplies a Vector3 by an int, or by another Vector3\n        if isinstance(other, Vector3):\n            return Vector3(self.x * other.x, self.y * other.y, self.z * other.z)\n        elif isinstance(other, (int, float)):\n            return Vector3(self.x * other, self.y * other, self.z * other)\n        else:\n            raise ValueError(\"Accepted data types are float, int and Vector3.\")\n\n    def __truediv__(self, other):  # returns a FLOAT value (true div)\n        if isinstance(other, Vector3):\n            return Vector3(self.x / other.x, self.y / other.y, self.z / other.z)\n        elif isinstance(other, (int, float)):\n            return Vector3(self.x / other, self.y / other, self.z / other)\n        else:\n            raise ValueError(\"Accepted data types are float, int and Vector3.\")\n\n    def __floordiv__(self, other):  # returns an INT value (floor div)\n        if isinstance(other, Vector3):\n            return Vector3(self.x // other.x, self.y // other.y, self.z // other.z)\n        elif isinstance(other, (int, float)):\n            return Vector3(self.x // other, self.y // other, self.z // other)\n        else:\n            raise ValueError(\"Accepted data types are float, int and Vector3.\")\n\n    def __pos__(self):\n        return Vector3(abs(self.x), abs(self.y), abs(self.z))\n\n    def __neg__(self):\n        return Vector3(-self.x, -self.y, -self.z)\n\n    def __eq__(self, other):\n        return self.x == other.x and self.y == other.y and self.z == other.z\n\n    def Magnitude(self):  # returns the magnitude of the Vector3. (length of the vector)\n        return ((self.x ** 2) + (self.y ** 2) + (self.z ** 2)) ** (1 / 2)  # sqrt\n"
  },
  {
    "path": "README.md",
    "content": "# credits_public\n- Public version of Frums - Credits animation repository.\n- Should be multiplatform. Feel free to raise an issue if it isn't. Feel free to raise an issue for anything.\n- Tested on clean Python 3.6 (Windows 10) with minimal modules installed.\n \n \n# media credits\n- All animation work done by me (plaaosert)\n- Renderer used is an in-progress command line rendering library for which a separate repository will be created... eventually.\n- Song credit: Frums - Credits EX https://soundcloud.com/frums/credits-ex\n- Song is not included in this repository. Read /media/README.txt.\n\n \n# how to see\n- https://youtu.be/o3cKQzrtFgQ\n \n \n# how to run\n Run `credits.py`. Required libraries:\n - just-playback https://github.com/cheofusi/just_playback\n - keyboard https://github.com/boppreh/keyboard\n   - **important**: if you're running **linux** or **macos**, you might have trouble with this module. if so, try using `credits_pynput.py` instead (and install pynput -- https://pypi.org/project/pynput/)\n - colorama https://github.com/tartley/colorama -\n the version of colorama used is also included inside this repository.\n\n You can install all required libraries by running:\n ```\n pip install -r requirements.txt\n ```\n\n# misc\n\n## start-of-song skips\nbefore the animation starts, a menu will appear with options to skip to specific parts of the song. i used this a lot while debugging and left it in because i ~forgot~ felt i should include it. input nothing to let the song start normally, else hold a digit key on the keyboard to skip:\n- 1 | start _(equivalent to pressing no button but slightly faster)_\n- 2 | title _(flashing square in center + artist and self credits)_ \n- 3 | funding _(start of first \"Funding for this program was made possible...\" section)_ \n- 4 | loading _(loading bar before fake error and second typography \"searching for access point\" segment)_\n- 5 | break _(hell ocean, second weather forecast scene)_\n- 6 | final _(return of the flashing square - ending sequence)_\n\n## generic hotkeys\n| hotkey              | effect                   |\n| ------------------- | ------------------------ |\n| `p`                 | play/pause toggle        |\n| `,`                 | small fast forward (4x)  |\n| `.`                 | medium fast forward (8x) |\n| `/`                 | large fast forward (16x) |\n"
  },
  {
    "path": "animation_classes.py",
    "content": "# Contains all classes (just Weather)\nimport random\nimport math\n\n\nclass Weather:\n    def __init__(self, precip, temp, wind, gust, wind_dir, humidity, days=2):\n        self.precip = precip\n        self.temp = temp\n        self.wind = wind\n        self.gust = gust\n        self.wind_dir = wind_dir\n        self.humidity = humidity\n        self.weather_name = self.get_weather_name()\n        self.days = days\n\n    def __str__(self):\n        return \"{}% precipitation\\n\" \\\n               \"{}F temperature\\n\" \\\n               \"{}mph wind\\n\" \\\n               \"{}mph gust\\n\" \\\n               \"{} wind direction\\n\" \\\n               \"{}% humidity\\n\" \\\n               \"{}\\n\".format(\n                    round(self.precip * 100, 2),\n                    int(self.temp),\n                    int(self.wind),\n                    int(self.gust),\n                    int(self.wind_dir),\n                    round(self.humidity * 100, 2),\n                    self.weather_name\n               )\n\n    def get_weather_name(self):\n        # Clear/Sunny <> Cloudy/Rainy\n        if self.humidity > 0.5:\n            # Cloudy or rainy\n            if self.precip > 0.4:\n                if self.wind > 43:\n                    return \"Blizzard\" if self.temp < 32 else \"Hurricane\"\n                elif self.wind > 25:\n                    return \"Snowstorm\" if self.temp < 32 else \"Storm\"\n                else:\n                    return \"Snow\" if self.temp < 32 else \"Rain\"\n            elif self.precip > 0.25:\n                return \"Sleet\" if self.temp < 32 else \"Drizzle\"\n\n            if self.humidity > 0.8 or self.precip > 0.5:\n                return \"Overcast\"\n            elif self.humidity > 0.65 or self.precip > 0.3:\n                return \"Cloudy\"\n            else:\n                return \"Partly cloudy\"\n        else:\n            if self.precip > 0.4:\n                return \"Snow\" if self.temp < 32 else \"Rain\"\n            elif self.precip > 0.2:\n                return \"Sleet\" if self.temp < 32 else \"Drizzle\"\n\n            return (\"Sunny\" if self.humidity < 0.1 else \"Partly sunny\") if self.humidity < 0.2 else \"Clear\"\n\n    def mutate(self, steps=1):\n        for i in range(steps):\n            # Prefer 33% precip, move wind dir by +-3 each day, gust = speed x 2.1 - 2.6\n            precip_move = random.randint(-100, 100) / 400.0\n            precip_move += (random.randint(0, int(100 * abs(0.33 - self.precip))) / 200) * (-1 if 0.33 - self.precip < 0 else 1)\n            self.precip = min(1, max(0, self.precip + precip_move))\n\n            self.wind_dir = (self.wind_dir + random.randint(-3, 3)) % 8\n\n            wind_move = random.randint(-100, 100) / 13\n            wind_move += (random.randint(0, int(abs(15 - self.wind))) / 3) * (-1 if 15 - self.wind < 0 else 1)\n            self.wind = max(0, self.wind + wind_move)\n\n            adjusted_days = self.days - 282\n            temp_target = 40 * (math.sin((2 * math.pi * adjusted_days) / 365 - math.pi / 3) + 1) + 20\n            temp_move = random.randint(-100, 100) / 20\n            temp_move += (random.randint(0, int(abs(temp_target - self.temp))) / 5) * (-1 if temp_target - self.temp < 0 else 1)\n            self.temp = max(0, self.temp + temp_move)\n\n            humidity_move = random.randint(-100, 100) / 500.0\n            humidity_move += (random.randint(0, int(abs(0.2 - self.humidity))) / 200) * (-1 if 0.5 - self.humidity < 0 else 1)\n            self.humidity = max(0, min(1, self.humidity + humidity_move))\n\n            self.gust = self.wind * random.randint(210, 260) * 0.01\n            self.days += 1\n\n        self.weather_name = self.get_weather_name()\n\n\n# 22.10.2009: Cloudy, precip 20%, temp 43, wind dir 6, 13 (25)\n# 23.10.2009: Sunny, precip 4%, temp 52, wind dir 7, 12 (25)\n# 24.10.2009: Partly sunny, precip 7%, temp 48, wind dir 1, 8 (20)\nknown_weathers = (\n    Weather(0.203, 43, 13, 25, 6, 0.66),\n    Weather(0.04, 52, 12, 25, 7, 0.1),\n    Weather(0.07, 48, 8, 20, 1, 0.1, 200),\n    Weather(0.07, 48, 8, 20, 1, 0.1, 528 + 200),\n    Weather(-1, -1, -1, -1, -1, -1)\n)\n\nknown_weathers[4].weather_name = \"Connection lost...      \"\n"
  },
  {
    "path": "animation_functions.py",
    "content": "# Contains functions used inside credits.py.\n# Reduce clutter. Reuse clutter. Recycle clutter.\nimport random\nfrom CLIRender.dat import Vector2\nfrom colorama import Fore, Style\n\n\ndef generate_random_hex(length):\n    return ('%0' + str(length) + 'x') % random.randrange(16 ** length)\n\n\ndef replace_text_with_spaces(string, chance):\n    return \"\".join((\" \" if random.randint(0, 100) < chance else char) for char in string)\n\n\ndef render_weather(c, layer, x, y, weather, mutations_after=0, spc_chance=0):\n    # .                      .\n    if weather.precip != -1:\n        c.set_string(\n            layer, Vector2(x + 5, y - 2),\n            replace_text_with_spaces(\"{:>14}\".format(weather.weather_name), spc_chance),\n            Fore.YELLOW + Style.NORMAL\n        )\n    else:\n        c.set_string(\n            layer, Vector2(x, y - 2),\n            replace_text_with_spaces(\"{:>14}\".format(weather.weather_name), spc_chance),\n            Fore.RED + Style.BRIGHT\n        )\n\n    temp_colour = (Fore.WHITE, Fore.CYAN, Fore.YELLOW, Fore.RED)[min(3, int(weather.temp) // 23)]\n    c.set_string(\n        layer, Vector2(x, y),\n        replace_text_with_spaces(\"{:2}°F  \".format(int(weather.temp)), spc_chance),\n        temp_colour + Style.BRIGHT\n    )\n\n    wind_dirs = (\"N \", \"NE\", \"E \", \"SE\", \"S \", \"SW\", \"W \", \"NW\")\n    c.set_string(\n        layer, Vector2(x + 5, y),\n        replace_text_with_spaces(\"Wind {:2} mph {}\".format(int(weather.wind), wind_dirs[weather.wind_dir]), spc_chance),\n        Fore.CYAN + Style.BRIGHT\n    )\n\n    c.set_string(\n        layer, Vector2(x + 5, y + 2),\n        replace_text_with_spaces(\"Precipitation \", spc_chance),\n        Fore.BLUE + Style.BRIGHT\n    )\n\n    c.set_string(\n        layer, Vector2(x + 5, y + 3),\n        replace_text_with_spaces(\"{:^13} \".format(str(round(weather.precip * 100, 2)) + \"%\"), spc_chance),\n        Fore.BLUE + Style.BRIGHT\n    )\n\n    weather.mutate(mutations_after)\n\n\ndef noise(c, layer, amount, chars, colours):\n    for n in range(amount):\n        location = Vector2(random.randint(0, c.dimensions.x - 1), random.randint(0, c.dimensions.y - 1))\n        c.set_char(\n            layer, location, random.choice(chars), random.choice(colours)\n        )\n\n\ndef set_multiline_string(c, layer, x, y, string, col):\n    for offset, line in enumerate(string.split(\"\\n\")):\n        c.set_string(\n            layer, Vector2(x, y + offset), line, col\n        ),\n\n\ndef type_text(c, generator, layer, x, y, col, render=True):\n    # pop a char off the manager's text if there is one\n    text_get = generator.get_data(\"text\")\n    offset = generator.get_data(\"offset\")\n    total_chars = 0\n    add_offset = 0\n    if text_get:\n        if text_get.startswith(\"[##CLEAR|\"):\n            clear_bounds = text_get.split(\"|\")[1].split(\";\")\n            for yclear in range(int(clear_bounds[1])):\n                location = Vector2(x, y + yclear)\n\n                if render:\n                    c.set_string(\n                        layer, location, \" \" * int(clear_bounds[0]), col\n                    )\n        else:\n            for linecount, text_line in enumerate(text_get.split(\"\\n\")):\n                local_offset = offset - total_chars\n                if 0 <= local_offset < len(text_get):\n                    place_typer = local_offset < len(text_line) - 1\n                    string = text_line[:local_offset] + (\"_\" if place_typer else \"\")\n                    if local_offset < len(text_line) and text_line[local_offset] == \"@\":\n                        add_offset += 3\n\n                    string = string.replace(\"~\", \"\").replace(\"@\", \"\")\n                    total_chars += len(text_line)\n                    location = Vector2(x, y + linecount)\n\n                    if render:\n                        c.set_string(\n                            layer, location, string, col\n                        )\n\n            generator.oper_data(\"offset\", lambda t: t + 1 + add_offset)\n\n\ndef fuck_up_text(string, chance, also_ignore=\"\"):\n    new_str = \"\"\n    fucks = (\n        \".\", \".\", \".\", \" \", \" \", \"`\", \"=\", \"/\", \"?\", \"-\", \"$\", \"%\"\n    )\n    for char in string:\n        if char == \"\\n\":\n            new_str += char\n        else:\n            if random.randint(1, 1000) < chance and char not in \"@~\\n\" + also_ignore:\n                new_str += random.choice(fucks)\n            else:\n                new_str += char\n\n    return new_str\n\n\ndef debug_info(c, g, b, frames):\n    c.set_string(\n        0, Vector2(32, 1), \"{:4} g | {:4} l\".format(g.parent.cur_beat, g.parent.active_scene[0].internal_beat), Style.BRIGHT + Fore.YELLOW\n    ),\n    counted_scenes = 0\n    for index, scene in enumerate(filter(lambda s: s.name != \"debug_counter\", g.parent.active_scene)):\n        c.set_string(\n            0, Vector2(32, index + 2), \"{:^17}\".format(\n                scene.name + \" ({})\".format(len(list(filter(lambda g: g.start_beat <= b, scene.generators))))\n            ), Style.NORMAL + Fore.GREEN\n        ),\n\n        counted_scenes += 1\n\n    for index2 in range(counted_scenes, 6):\n        c.set_string(\n            0, Vector2(32, index2 + 2), \"                 \", Style.NORMAL + Fore.GREEN\n        ),\n\n    c.set_string(\n        0, Vector2(32, 8), \"  {:4} e/s\".format(c.edits_this_frame), Style.BRIGHT + Fore.YELLOW\n    ),\n\n    avg_differences = sum(frames[i] - frames[i - 1] for i in range(len(frames) - 1, 0, -1))\n    if avg_differences:\n        avg_differences /= 10\n    else:\n        avg_differences = 60\n\n    c.set_string(\n        0, Vector2(32, 9), \" {:6} fps\".format(round(1 / avg_differences, 1)), Style.BRIGHT + Fore.YELLOW\n    ),\n\n    cols = (\n        Fore.BLACK,\n        Fore.RED,\n        Fore.GREEN,\n        Fore.YELLOW,\n        Fore.BLUE,\n        Fore.MAGENTA,\n        Fore.CYAN,\n        Fore.WHITE,\n    )\n\n    styles = (\n        Style.NORMAL,\n        Style.BRIGHT\n    )\n    for index in range(16):\n        c.set_char(\n            0, Vector2(32 + (index % 8), 11 + (index // 8)), \"##\", cols[index % 8] + styles[index // 8]\n        ),\n\n\ndef clear(c, layer):\n    c.clear_layer(layer)\n\n\ndef beat_toggle(c, g, layer, x, x2, y, y2, char, col):\n    tog = g.get_data(\"beat_toggle\")\n    chars = char if tog else \"..\"\n    x_diff = x2 - x\n    for yn in range(y, y2):\n        c.set_string(\n            layer, Vector2(x, yn), chars * x_diff, col\n        ),\n\n    g.set_data(\"beat_toggle\", not tog)\n\n\ndef work_out_date(b, day_offset=0):\n    # \"22.10.2009\"\n    lengths = (\n        31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31\n    )\n\n    # One day passes every 64 beats.\n    days_needed = (b // 64) + day_offset\n\n    month_loc = 9\n    day_loc = 22\n    year = 2009\n    while days_needed > 0:\n        leap_year = (1 if year % 4 == 0 and month_loc == 1 else 0)  # leap year\n        until_end_month = lengths[month_loc] + 1 - day_loc + leap_year\n        day_loc += min(days_needed, until_end_month)\n        days_needed -= min(days_needed, until_end_month)\n\n        if day_loc > lengths[month_loc] + leap_year:\n            month_loc += 1\n            day_loc = 1\n            if month_loc >= 12:\n                month_loc = 0\n                year += 1\n\n    return \"{:02}.{:02}.{:04}\".format(day_loc, month_loc + 1, year)\n\n\ndef split_word_template(string):\n    return [sp.split(\"#\") for sp in string.split(\"\\n\")]\n\n\ndef typewrite_by_word(c, generator, layer, x, y, col, render=True, history_var=\"history\"):\n    # Show the text up to offset words at line lineno\n    text_get = generator.get_data(\"text\")\n    offset = generator.get_data(\"offset\")\n    lineno = generator.get_data(\"lineno\")\n    if text_get:\n        if lineno < len(text_get):\n            line_get = text_get[lineno]\n            line_total = \"\".join(line for line in line_get[:offset])\n\n            if line_get:\n                if line_get[0].startswith(\"[~~CLEAR|\"):\n                    clear_bounds = line_get[0].split(\"|\")[1]\n                    location = Vector2(x, y)\n\n                    if render:\n                        c.set_string(\n                            layer, location, \" \" * int(clear_bounds), col\n                        )\n                else:\n                    # print line_total and increment offset by 1.\n                    # if offset is > lineno, reset offset and increment lineno\n                    # if line_total is empty (offset == 0), print a clear\n                    if render:\n                        if line_total:\n                            c.set_string(\n                                layer, Vector2(x, y), line_total.replace(\"~\", \"\"), col\n                            ),\n                        else:\n                            c.set_string(\n                                layer, Vector2(x, y), \" \" * 60, col\n                            ),\n\n                    if offset >= len(line_get):\n                        history = generator.parent.get_data(history_var)\n                        is_fluff = line_total.startswith(\" \") or not line_total\n                        is_important = line_total.endswith(\"~\")\n                        colour_select = Fore.YELLOW + Style.NORMAL\n                        prefix = \"- \"\n                        if is_fluff:\n                            prefix = \"  \"\n                            colour_select = Fore.BLACK + Style.BRIGHT\n                        elif is_important:\n                            prefix = \"> \"\n                            colour_select = Fore.GREEN + Style.NORMAL\n\n                        if not history:\n                            history = [(\n                                prefix + line_total.strip(\" \").replace(\"~\", \"\"), colour_select\n                            )]\n                            generator.parent.set_data(history_var, history)\n                        else:\n                            history.append((\n                                prefix + line_total.strip(\" \").replace(\"~\", \"\"), colour_select\n                            ))\n\n                        generator.parent.set_data(\"refresh\", True)\n\n                        generator.set_data(\"offset\", 0)\n                        generator.set_data(\"lineno\", lineno + 1)\n                    else:\n                        generator.set_data(\"offset\", offset + 1)\n\n            else:\n                generator.set_data(\"offset\", offset + 1)\n\n\ndef write_history(c, generator, layer, x, y, col, stop, var=\"history\"):\n    # Write history from y position going upwards until 0\n    history = generator.parent.get_data(var)\n    need_refresh = generator.parent.get_data(\"refresh\")\n\n    if need_refresh:\n        generator.parent.set_data(\"refresh\", False)\n\n        if history:\n            lineid = 0\n            for ypos in range(y, stop - 1, -1):\n                line = history[len(history) - 1 - lineid] if lineid < len(history) else (\"\", Fore.BLACK + Style.BRIGHT)\n                lineid += 1\n\n                location = Vector2(x, ypos)\n\n                c.set_string(\n                    layer, location, \"{:50}\".format(line[0]), line[1]\n                ),\n        else:\n            for ypos in range(y, stop - 1, -1):\n                c.set_string(\n                    layer, Vector2(x, ypos), \" \" * 50, Fore.BLACK + Style.NORMAL\n                ),\n\n\ndef show_access_point_visual(c, generator, layer, x, y):\n    # Increment counter. If counter > 8, reset it, increment the bigger counter\n    counter = generator.get_data(\"counter\")\n    block_counter = generator.get_data(\"block\")\n    location_x = 5 * (block_counter % 6) + x\n    location_y = 4 * (block_counter // 6) + y\n\n    if counter < 8:\n        set_multiline_string(\n            c, layer, location_x, location_y,\n            \"  ###  \\nPBS #{:02}\\nPing  {}\".format(block_counter + 1, counter + 1), Fore.YELLOW + Style.NORMAL\n        )\n\n        generator.oper_data(\"counter\", lambda t: t + 1)\n    else:\n        set_multiline_string(\n            c, layer, location_x, location_y,\n            \"  ...  \\nPBS #{:02}\\n-------\".format(block_counter + 1), Fore.BLACK + Style.BRIGHT\n        )\n\n        location_x_next = 5 * ((block_counter + 1) % 6) + x\n        location_y_next = 4 * ((block_counter + 1) // 6) + y\n\n        set_multiline_string(\n            c, layer, location_x_next, location_y_next,\n            \"  ###  \\nPBS #{:02}\\nPing  {}\".format(block_counter + 2, 1), Fore.YELLOW + Style.NORMAL\n        )\n\n        generator.set_data(\"counter\", 1)\n        generator.oper_data(\"block\", lambda t: t + 1)\n\n\ndef make_poweroff_bars(c, b, layer, col):\n    # at b=1, full screen\n    # then lerp height on both ends down to 0\n    height = int(24 / (b ** 1.3))\n    location = Vector2(0, 12 - (height // 2))\n\n    c.set_string(\n        layer, location, (\"##\" * 40) * height, col\n    )"
  },
  {
    "path": "animation_scenes.py",
    "content": "# Contains all animation scenes except for debug_counter.\n# Use variable \"all_scenes\" which contains all scenes defined here.\n# This file also creates the canvas.\n\nimport animator as am\nfrom animation_functions import *\nfrom animation_classes import known_weathers\nfrom ocean import begin_ocean, update_ocean_slices\nfrom CLIRender.classes import Canvas\n\ncanvas = Canvas(Vector2(40, 24), 1, ())\n\nwipe = am.Scene(\n    \"wipe\",\n    (\n        am.Generator(\n            0, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: noise(\n                canvas, 0, int(b ** 1.4), (\"##\", \"@@\", \"  \"),\n                (\n                    *((Style.BRIGHT + Fore.WHITE,) * b),\n                    *((Style.NORMAL + Fore.WHITE,) * (70 - b)),\n                    *((Style.BRIGHT + Fore.BLACK,) * 4 * (40 - b)),\n                )\n            ),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\nclear_wipe = am.Scene(\n    \"clear_wipe\",\n    (\n        am.Generator(\n            0, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: noise(\n                canvas, 0, int(b ** 2.2), (\"  \",), (Fore.WHITE + Style.BRIGHT,)\n            ),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\nclear_scene = am.Scene(\n    \"clear\",\n    (\n        #am.Generator(\n        #    0, am.Generator.at_beat(0),\n        #    lambda g: noise(\n        #        canvas, 0, 40 * 24, (\"##\",), (Style.BRIGHT + Fore.WHITE,)\n        #    ),\n        #    am.Generator.no_request(), am.Generator.no_request()\n        #),\n\n        am.Generator(\n            0, am.Generator.always(),\n            lambda g: clear(canvas, 0),\n            am.Generator.no_request(), am.Generator.no_request()\n        ),\n    )\n)\n\n\nocean = am.Scene(\n    \"ocean_b\",\n    (\n        am.Generator(\n            0, am.Generator.every_n_beats(2),\n            lambda g: g.set_data(\"ocean\", begin_ocean(), \"ocean_glitch\", 1, \"ocean_col\", Style.BRIGHT + Fore.BLUE),\n            lambda g, b: canvas.set_string(\n                0, Vector2(0, 14), update_ocean_slices(g.get_data(\"ocean\"),\n                                                       g.get_data(\"ocean_glitch\")), g.get_data(\"ocean_col\")\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.always(),\n            lambda g: g.set_data(\"text\", \"\", \"offset\", 0),\n            lambda g, b: type_text(canvas, g, 0, 1, 1, Style.BRIGHT + Fore.WHITE),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\nocean2 = am.Scene(\n    \"ocean_c\",\n    (\n        am.Generator(\n            0, am.Generator.every_n_beats(2),\n            lambda g: g.set_data(\"ocean\", begin_ocean(), \"ocean_glitch\", 16, \"ocean_col\", Style.BRIGHT + Fore.BLACK),\n            lambda g, b: canvas.set_string(\n                0, Vector2(0, 14), update_ocean_slices(g.get_data(\"ocean\"),\n                                                       g.get_data(\"ocean_glitch\")), g.get_data(\"ocean_col\")\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.always(),\n            lambda g: g.set_data(\"text\", \"\", \"offset\", 0),\n            lambda g, b: type_text(canvas, g, 0, 1, 1, Style.BRIGHT + Fore.RED, int(b ** 1.143) % 20 not in (0, 8, 17, 15)),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.always(),\n            lambda g: g.set_data(\"text\", \"\", \"offset\", 0),\n            lambda g, b: type_text(canvas, g, 0, 1, 1, Style.BRIGHT + Fore.RED, int(b ** 1.2) % 20 in (0, 15)),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.always(),\n            lambda g: g.set_data(\"text\", \"\", \"offset\", 0),\n            lambda g, b: type_text(canvas, g, 0, 1, 1, Style.BRIGHT + Fore.RED, int(b ** 1.2) % 20 == 8),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.always(),\n            lambda g: g.set_data(\"text\", \"\", \"offset\", 0),\n            lambda g, b: type_text(canvas, g, 0, 1, 1, Style.BRIGHT + Fore.RED, int(b ** 1.2) % 20 == 17),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\nocean3 = am.Scene(\n    \"ocean_d\",\n    (\n        am.Generator(\n            0, am.Generator.always(),\n            lambda g: g.set_data(\"ocean\", begin_ocean(), \"ocean_glitch\", 100, \"ocean_col\", Style.NORMAL + Fore.MAGENTA),\n            lambda g, b: canvas.set_string(\n                0, Vector2(0, 14), update_ocean_slices(g.get_data(\"ocean\"),\n                                                       g.get_data(\"ocean_glitch\")), g.get_data(\"ocean_col\")\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(canvas, 0, 1, 1,\n                                              \"The system has encountered a fatal error. Please wait.\\n\\n\"\n                                              \"[ERR: 801]\\n\\n\" + \"\\n\".join(\n                                                  \" \".join(generate_random_hex(4) for _ in range(8)) for _ in range(4)\n                                              ),\n                                              Style.BRIGHT + Fore.RED),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\ntext = am.Scene(\n    \"typewrite\",\n    (\n        am.Generator(\n            0, am.Generator.always(),\n            lambda g: g.set_data(\"text\", \"\", \"offset\", 0),\n            lambda g, b: type_text(canvas, g, 0, 1, 1, Style.BRIGHT + Fore.WHITE),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\ntitle = am.Scene(\n    \"title\",\n    (\n        # We have 12 beat-ends to map to:\n        # '  - plaaosert\n        # '  - frums\n        # '  - python 3.6\n        # '  - command line\n        # drums\n        # '  -\n        # '  -\n        # '  -\n        # '  -\n        # small moog\n        # '  -\n        # '  -\n        # '  -\n        # '  -\n\n        am.Generator(\n            64, am.Generator.at_beat(64),\n            am.Generator.no_create(),\n            lambda g, b: canvas.set_string(\n                0, Vector2(1, 1), \"animation | plaaosert\", Fore.CYAN + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            128, am.Generator.at_beat(128),\n            am.Generator.no_create(),\n            lambda g, b: canvas.set_string(\n                0, Vector2(1, 2), \"bgm       | Frums - Credits\", Fore.CYAN + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            192, am.Generator.at_beat(192),\n            am.Generator.no_create(),\n            lambda g, b: canvas.set_string(\n                0, Vector2(1, 4), \"running pure Python 3.6\", Fore.CYAN + Style.NORMAL\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            256, am.Generator.at_beat(256),\n            am.Generator.no_create(),\n            lambda g, b: canvas.set_string(\n                0, Vector2(1, 5), \"in the command line\", Fore.CYAN + Style.NORMAL\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            320, am.Generator.at_beat(320),\n            am.Generator.no_create(),\n            lambda g, b: canvas.set_string(\n                0, Vector2(0, 21), \"--\" * 40, Fore.WHITE + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            384, am.Generator.at_beat(384),\n            am.Generator.no_create(),\n            lambda g, b: canvas.set_string(\n                0, Vector2(1, 22), \"> _ \", Fore.WHITE + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            448, am.Generator.at_beat(448),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(\n                canvas, 0, 26, 14, \"----------------------------\\n\" +\n                                   \"|                          |\\n\" * 6, Fore.WHITE + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            512, am.Generator.at_beat(512),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(\n                canvas, 0, 27, 15, \"22.10.2009\", Fore.YELLOW + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            576, am.Generator.at_beat(576),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(\n                canvas, 0, 26, 16, \"----------------------------\", Fore.WHITE + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            640, am.Generator.at_beat(640),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(\n                canvas, 0, 27, 17, \"43°F      Wind 13 mph W \\n\"\n                                   \"                        \", Fore.CYAN + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            704, am.Generator.at_beat(704),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(\n                canvas, 0, 32, 19, \"Precipitation \\n\"\n                                   \"    20.3%     \", Fore.BLUE + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            768, am.Generator.at_beat(768),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(\n                canvas, 0, 36, 15, \"Cloudy\", Fore.YELLOW + Style.NORMAL\n            ),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\nbeats = am.Scene(\n    \"beats\",\n    (\n        # beat manager\n        am.Generator(\n            0, am.Generator.combine_conditions(am.Generator.every_on_off(48, 16), am.Generator.every_n_beats(4)),\n            lambda g: g.set_data(\"beat_toggle\", True),\n            lambda g, b: beat_toggle(canvas, g, 0, 18, 22, 11, 15, \"@@\", Style.BRIGHT + Fore.YELLOW),\n            am.Generator.no_request()\n        ),\n        am.Generator(\n            0, am.Generator.combine_conditions(am.Generator.every_off_on(48, 16), am.Generator.every_n_beats(2)),\n            lambda g: g.set_data(\"beat_toggle\", True),\n            lambda g, b: beat_toggle(canvas, g, 0, 18, 22, 11, 15, \"##\", Style.BRIGHT + Fore.GREEN),\n            am.Generator.no_request()\n        ),\n    )\n)\n\nbeats_lr = am.Scene(\n    \"beats_lr\",\n    (\n        # beat manager\n        am.Generator(\n            0, am.Generator.combine_conditions(am.Generator.every_on_off(48, 16), am.Generator.every_n_beats(4)),\n            lambda g: g.set_data(\"beat_toggle\", True),\n            lambda g, b: beat_toggle(canvas, g, 0, 18, 20, 11, 15, \"@@\", Style.BRIGHT + Fore.YELLOW),\n            am.Generator.no_request()\n        ),\n        am.Generator(\n            0, am.Generator.combine_conditions(am.Generator.every_off_on(48, 16), am.Generator.every_n_beats(2)),\n            lambda g: g.set_data(\"beat_toggle\", True),\n            lambda g, b: beat_toggle(canvas, g, 0, 20, 22, 11, 15, \"##\", Style.BRIGHT + Fore.GREEN),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\nbeats_side = am.Scene(\n    \"beats_side\",\n    (\n        # beat manager\n        am.Generator(\n            0, am.Generator.combine_conditions(am.Generator.every_on_off(48, 16), am.Generator.every_n_beats(4)),\n            lambda g: g.set_data(\"beat_toggle\", True),\n            lambda g, b: beat_toggle(canvas, g, 0, 18, 20, 11, 15, \"@@\", Style.BRIGHT + Fore.YELLOW),\n            am.Generator.no_request()\n        ),\n        am.Generator(\n            0, am.Generator.combine_conditions(am.Generator.every_off_on(48, 16), am.Generator.every_n_beats(2)),\n            lambda g: g.set_data(\"beat_toggle\", True),\n            lambda g, b: beat_toggle(canvas, g, 0, 20, 22, 11, 15, \"##\", Style.BRIGHT + Fore.GREEN),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\ndate_ticker = am.Scene(\n    \"dates\",\n    (\n        # TODO change this shit to every n beats, use a data variable to store the current date index PLEASE\n        #\n        # Run at normal speed\n        # Run at double speed\n        # Run with slight randomness forwards\n        # Skip forwards, go every step\n        # At crazy part, switch off this and go to a glitchy display\n        #\n        am.Generator(\n            0, am.Generator.before_n(64 * 8),\n            am.Generator.no_create(),\n            lambda g, b: canvas.set_string(\n                0, Vector2(27, 15), work_out_date(b), Fore.YELLOW + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            64 * 8, am.Generator.before_n(64 * 12 - 4),\n            am.Generator.no_create(),\n            lambda g, b: canvas.set_string(\n                0, Vector2(27, 15), work_out_date((64 * 8) + (b - (64 * 8)) * 2), Fore.YELLOW + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            64 * 12 - 4, am.Generator.at_beat(64 * 12 - 4),\n            am.Generator.no_create(),\n            lambda g, b: canvas.set_string(\n                0, Vector2(27, 15), \"??.??.????\", Fore.YELLOW + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            64 * 12 + 4, am.Generator.combine_conditions(am.Generator.every_n_beats(4), am.Generator.before_n(64 * 16)),\n            am.Generator.no_create(),\n            lambda g, b: canvas.set_string(\n                0, Vector2(27, 15), work_out_date(random.randint(64 * 12 + b * 8, 64 * 12 + b * 24)), Fore.YELLOW + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            64 * 16, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: canvas.set_string(\n                0, Vector2(27, 15), work_out_date(64 * 16 + b * 64 - 64 * 16 * 32), Fore.YELLOW + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\nredraw_ui = am.Scene(\n    \"redraw_ui\",\n    (\n        am.Generator(\n            0, am.Generator.at_beat(0),\n            am.Generator.no_create(),\n            lambda g, b: canvas.set_string(\n                0, Vector2(0, 21), \"--\" * 40, Fore.WHITE + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.at_beat(0),\n            am.Generator.no_create(),\n            lambda g, b: canvas.set_string(\n                0, Vector2(1, 22), \"> _ \", Fore.WHITE + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.at_beat(2),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(\n                canvas, 0, 26, 14, \"----------------------------\\n\" +\n                                   \"|                          |\\n\" * 6, Fore.WHITE + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.at_beat(3),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(\n                canvas, 0, 27, 15, \"22.10.2009\", Fore.YELLOW + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.at_beat(4),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(\n                canvas, 0, 26, 16, \"----------------------------\", Fore.WHITE + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.at_beat(5),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(\n                canvas, 0, 27, 17, \"43°F      Wind 13 mph W \", Fore.CYAN + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.at_beat(6),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(\n                canvas, 0, 32, 19, \"Precipitation \\n\"\n                                   \"    20.3%     \", Fore.BLUE + Style.BRIGHT\n            ),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.at_beat(7),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(\n                canvas, 0, 36, 15, \"Cloudy\", Fore.YELLOW + Style.NORMAL\n            ),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\nweather = am.Scene(\n    \"weather\",\n    (\n        am.Generator(\n            0, am.Generator.combine_conditions(am.Generator.every_n_beats(64), am.Generator.before_n(64 * 8)),\n            am.Generator.no_create(),\n            lambda g, b: render_weather(canvas, 0, 27, 17, known_weathers[min(2, b // 64)], 0 if b < 128 else 1),\n            am.Generator.no_request()\n        ),\n        am.Generator(\n            64 * 8, am.Generator.combine_conditions(am.Generator.every_n_beats(32), am.Generator.before_n(64 * 12)),\n            am.Generator.no_create(),\n            lambda g, b: render_weather(canvas, 0, 27, 17, known_weathers[2], 1),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            64 * 12 - 4, am.Generator.at_beat(64 * 12 - 4),\n            am.Generator.no_create(),\n            lambda g, b: render_weather(canvas, 0, 27, 17, known_weathers[4], 1),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            64 * 12 + 4, am.Generator.combine_conditions(am.Generator.every_n_beats(4), am.Generator.before_n(64 * 16)),\n            am.Generator.no_create(),\n            lambda g, b: render_weather(canvas, 0, 27, 17, known_weathers[2], 14),\n            am.Generator.no_request()\n        ),\n        am.Generator(\n            64 * 16, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: render_weather(canvas, 0, 27, 17, known_weathers[3], 1, max(0, (b - 1080) * 2.2)),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\nfunding = am.Scene(\n    \"funding\",\n    (\n        am.Generator(\n            0, am.Generator.always(),\n            lambda g: g.set_data(\"text\", [], \"offset\", 0, \"lineno\", 0, \"type_col\", Fore.WHITE + Style.BRIGHT),\n            lambda g, b: typewrite_by_word(canvas, g, 0, 2, 22, g.get_data(\"type_col\")),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: write_history(canvas, g, 0, 1, 19, Fore.BLACK + Style.BRIGHT, 1),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\nloading = am.Scene(\n    \"loadingbar\",\n    (\n        am.Generator(\n            0, am.Generator.at_beat(0),\n            lambda g: g.scene.set_data(\"progress\", 0),\n            lambda g, b: set_multiline_string(canvas, 0, 2, 9,\n                                              \"----------------------------------\\n\"\n                                              \"|                                |\\n\"\n                                              \"----------------------------------\",\n                                              Fore.WHITE + Style.BRIGHT),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            1, am.Generator.at_beat(1),\n            lambda g: g.scene.set_data(\"progress\", 0),\n            lambda g, b: set_multiline_string(canvas, 0, 3, 10,\n                                              \"          Loading...          \\n\",\n                                              Fore.BLACK + Style.BRIGHT),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.combine_conditions(am.Generator.before_n(240), am.Generator.every_n_beats(8)),\n            am.Generator.no_create(),\n            lambda g, b: g.scene.oper_data(\"progress\", lambda t: t + 1),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            240, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: g.scene.oper_data(\"progress\", lambda t: t + random.randint(40, 70)),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.before_n(240),\n            am.Generator.no_create(),\n            lambda g, b: canvas.set_string(0, Vector2(3, 10), \"#\" * g.scene.get_data(\"progress\"),\n                                           Fore.YELLOW + Style.BRIGHT),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            240, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: canvas.set_string(0, Vector2(3, 10), fuck_up_text(\"#\" * g.scene.get_data(\"progress\"), 400),\n                                           Fore.RED + Style.BRIGHT),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.before_n(240),\n            lambda g: g.set_data(\"text\", [], \"offset\", 0, \"lineno\", 0, \"type_col\", Fore.BLACK + Style.BRIGHT),\n            lambda g, b: typewrite_by_word(canvas, g, 0, 3, 12, g.get_data(\"type_col\")),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\nquick_loading = am.Scene(\n    \"fastload\",\n    (\n        am.Generator(\n            0, am.Generator.at_beat(0),\n            lambda g: g.scene.set_data(\"progress\", 0),\n            lambda g, b: set_multiline_string(canvas, 0, 2, 9,\n                                              \"----------------------------------\\n\"\n                                              \"|                                |\\n\"\n                                              \"----------------------------------\",\n                                              Fore.WHITE + Style.BRIGHT),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            1, am.Generator.at_beat(1),\n            lambda g: g.scene.set_data(\"progress\", 0),\n            lambda g, b: set_multiline_string(canvas, 0, 3, 10,\n                                              \"        Please wait...        \\n\",\n                                              Fore.BLACK + Style.BRIGHT),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: g.scene.oper_data(\"progress\", lambda t: t + random.randint(4, 6)),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: canvas.set_string(0, Vector2(3, 10), \"#\" * g.scene.get_data(\"progress\"),\n                                           Fore.GREEN + Style.BRIGHT),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\nerror_screen = am.Scene(\n    \"error\",\n    (\n        am.Generator(\n            0, am.Generator.at_beat(0),\n            lambda g: g.scene.set_data(\"progress\", 0),\n            lambda g, b: set_multiline_string(canvas, 0, 0, 14, \"--\" * 40 + \"\\n\" * 8 + \"  $ \", Fore.WHITE + Style.BRIGHT),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.at_beat(0),\n            lambda g: g.scene.set_data(\"progress\", 0),\n            lambda g, b: set_multiline_string(canvas, 0, 1, 1,\n                                              \"Automatic diagnosis unsuccessful. Please wait.\\n> \",\n                                              Fore.BLACK + Style.BRIGHT),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\nfunding_double = am.Scene(\n    \"fundingx2\",\n    (\n        am.Generator(\n            0, am.Generator.always(),\n            lambda g: g.set_data(\"text\", [], \"offset\", 0, \"lineno\", 0, \"type_col\", Fore.RED + Style.NORMAL),\n            lambda g, b: typewrite_by_word(canvas, g, 0, 2, 2, g.get_data(\"type_col\")),\n            am.Generator.no_request()\n        ),\n\n        # am.Generator(\n        #     0, am.Generator.always(),\n        #     am.Generator.no_create(),\n        #     lambda g, b: write_history(canvas, g, 0, 1, 19, Fore.BLACK + Style.BRIGHT, 1),\n        #     am.Generator.no_request()\n        # ),\n\n        am.Generator(\n            0, am.Generator.always(),\n            lambda g: g.set_data(\"text\", [], \"offset\", 0, \"lineno\", 0, \"type_col\", Fore.CYAN + Style.BRIGHT),\n            lambda g, b: typewrite_by_word(canvas, g, 0, 2, 22, g.get_data(\"type_col\"), history_var=\"history2\"),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: write_history(canvas, g, 0, 1, 21, Fore.BLACK + Style.BRIGHT, 15, var=\"history2\"),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.always(),\n            lambda g: g.set_data(\"text\", [], \"offset\", 0, \"lineno\", 0, \"type_col\", Fore.YELLOW + Style.BRIGHT),\n            lambda g, b: typewrite_by_word(canvas, g, 0, 4, 5, g.get_data(\"type_col\"), history_var=\"history3\"),\n            am.Generator.no_request()\n        ),\n    )\n)\n\naccess_points = am.Scene(\n    \"accesspoints\",\n    (\n        am.Generator(\n            0, am.Generator.combine_conditions(am.Generator.before_n(20), am.Generator.every_n_beats(2)),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(\n                canvas, 0, 1, 1,\n                \"\\n\\n\".join(\n                    \"\\n\".join(\n                        \"   \".join(\n                            (\"PBS #{:02}\".format((yblock * 6) + x + 1) if yline == 1 else (\"  ###  \", \"\", \"Unknown\")[yline]) if random.randint(0, max(1, 32 - int(b ** 1.2))) < 4 else \"       \"\n                            for x in range(6)\n                        ) for yline in range(3)\n                    ) for yblock in range(4)\n                ),\n                Fore.BLACK + Style.BRIGHT),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            20, am.Generator.at_beat(20),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(\n                canvas, 0, 1, 1,\n                \"\\n\\n\".join(\n                    \"\\n\".join(\n                        \"   \".join(\n                            (\"PBS #{:02}\".format((yblock * 6) + x + 1) if yline == 1 else (\"  ###  \", \"\", \"Unknown\")[yline])\n                            for x in range(6)\n                        ) for yline in range(3)\n                    ) for yblock in range(4)\n                ),\n                Fore.RED + Style.NORMAL),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.at_beat(0),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(canvas, 0, 1, 17,\n                                              \"No access points are broadcasting.\\n\"\n                                              \"Manual search in progress.\\n\"\n                                              \"Last search 27.02.2019 (532 days ago)\",\n                                              Fore.BLACK + Style.BRIGHT),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            80, am.Generator.combine_conditions(am.Generator.every_n_beats(8), am.Generator.before_n(976)),\n            lambda g: g.set_data(\"counter\", 0, \"block\", 0),\n            lambda g, b: show_access_point_visual(canvas, g, 0, 1, 1),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            968, am.Generator.at_beat(968),\n            am.Generator.no_create(),\n            lambda g, b: set_multiline_string(canvas, 0, 6, 9, \"  @@@  \\nPBS #14\\n Active\", Fore.GREEN + Style.BRIGHT),\n            am.Generator.no_request()\n        )\n    )\n)\n\n\nfunding_single = am.Scene(\n    \"fdg_single\",\n    (\n        am.Generator(\n            0, am.Generator.at_beat(0),\n            lambda g: g.scene.set_data(\"progress\", 0),\n            lambda g, b: set_multiline_string(canvas, 0, 0, 20, \"--\" * 40 + \"\\n\" + \"  Sending > \",\n                                              Fore.WHITE + Style.BRIGHT),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.always(),\n            lambda g: g.set_data(\"text\", [], \"offset\", 0, \"lineno\", 0, \"type_col\", Fore.YELLOW + Style.NORMAL),\n            lambda g, b: typewrite_by_word(canvas, g, 0, 6, 21, g.get_data(\"type_col\")),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\nfunding_down = am.Scene(\n    \"fdg_down\",\n    (\n        am.Generator(\n            0, am.Generator.always(),\n            lambda g: g.set_data(\"text\", [], \"offset\", 0, \"lineno\", 0, \"type_col\", Fore.YELLOW + Style.NORMAL),\n            lambda g, b: typewrite_by_word(canvas, g, 0, 1, 1 + g.get_data(\"lineno\"), g.get_data(\"type_col\")),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.always(),\n            lambda g: g.set_data(\"text\", [], \"offset\", 0, \"lineno\", 0, \"type_col\", Fore.YELLOW + Style.NORMAL),\n            lambda g, b: typewrite_by_word(canvas, g, 0, 1, 20 + g.get_data(\"lineno\"), g.get_data(\"type_col\")),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\npoweroff = am.Scene(\n    \"poweroff\",\n    (\n        am.Generator(\n            3, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: make_poweroff_bars(canvas, b - 2, 0, Fore.BLACK + Style.NORMAL),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            2, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: make_poweroff_bars(canvas, b - 1, 0, Fore.BLACK + Style.BRIGHT),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            1, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: make_poweroff_bars(canvas, b, 0, Fore.WHITE + Style.NORMAL),\n            am.Generator.no_request()\n        ),\n\n        am.Generator(\n            0, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: make_poweroff_bars(canvas, b + 1, 0, Fore.WHITE + Style.BRIGHT),\n            am.Generator.no_request()\n        ),\n    )\n)\n\nall_scenes = (\n    wipe, clear_wipe, clear_scene, ocean, ocean2, ocean3, text, title, beats, beats_lr, funding, date_ticker,\n    weather, redraw_ui, loading, quick_loading, error_screen, funding_double, access_points, funding_single,\n    funding_down, poweroff\n)\n"
  },
  {
    "path": "animator.py",
    "content": "class DataStoringObject:\n    def __init__(self):\n        self.data = {}\n\n    def set_data(self, *ident):\n        for i in range(0, len(ident), 2):\n            self.data[ident[i]] = ident[i + 1]\n\n    def get_data(self, ident):\n        if ident in self.data:\n            return self.data[ident]\n\n    def oper_data(self, ident, oper):\n        self.data[ident] = oper(self.data[ident])\n\n\nclass SceneManager(DataStoringObject):\n    def __init__(self, scenes, events):\n        super().__init__()\n\n        self.scenes = {scene.name: scene for scene in scenes}\n        self.events = {event.beat: [ev for ev in events if ev.beat == event.beat] for event in events}\n\n        for scene in scenes:\n            scene.set_parent(self)\n\n        self.active_scene = []\n        self.cur_beat = -1\n        self.data = {}\n\n    def start_scene(self, scene, at=0):\n        if len(self.active_scene) == 0:\n            self.active_scene.append(None)\n\n        self.active_scene[0] = self.scenes[scene]\n        self.active_scene[0].start(at)\n\n    def add_scene(self, scene, at=0):\n        self.active_scene.append(self.scenes[scene])\n        self.active_scene[-1].start(at)\n\n    def remove_scene(self, scene):\n        if self.scenes[scene] in self.active_scene:\n            self.active_scene.remove(self.scenes[scene])\n\n    def request_next(self, render=True):\n        for scene in self.active_scene:\n            scene.request_frame(render)\n\n        self.next_beat()\n\n    def next_beat(self):\n        self.cur_beat += 1\n        if self.cur_beat in self.events:\n            for event in self.events[self.cur_beat]:\n                event.do(self)\n\n    def set_scene_data(self, scene, *ident):\n        self.scenes[scene].set_data(*ident)\n\n    def set_generator_data(self, scene, generator, *ident):\n        self.scenes[scene].generators[generator].set_data(*ident)\n\n\nclass Event:\n    def __init__(self, beat, do):\n        self.beat = beat\n        self.do = do\n\n    @staticmethod\n    def swap_scene(sc, at=0):\n        return lambda sm: sm.start_scene(sc, at)\n\n    @staticmethod\n    def layer_scene(sc, at=0):\n        return lambda sm: sm.add_scene(sc, at)\n\n    @staticmethod\n    def remove_scene(sc):\n        return lambda sm: sm.remove_scene(sc)\n\n\nclass Scene(DataStoringObject):\n    def __init__(self, name, generators):\n        super().__init__()\n\n        self.parent = None\n        self.name = name\n        self.generators = generators\n        self.start_beat = 0\n        self.internal_beat = 0\n\n    def set_parent(self, parent):\n        self.parent = parent\n\n    # Requests a frame. This basically calls the request() and request_clear() functions in every generator.\n    def request_frame(self, render=True):\n        beat = self.internal_beat\n        if render:\n            for generator in self.generators:\n                if beat >= generator.start_beat and generator.condition(beat):\n                    if beat != self.start_beat and beat != generator.start_beat:\n                        # Clear previous beat\n                        generator.request_clear(generator, beat - 1)\n\n                    # Make current\n                    generator.request(generator, beat)\n\n        self.internal_beat += 1\n\n    # Starts the scene at the beat given.\n    def start(self, at):\n        for generator in self.generators:\n            generator.set_parent(self.parent)\n            generator.set_scene(self)\n            generator.on_create(generator)\n\n        self.start_beat = at\n        self.internal_beat = at\n        self.request_frame()\n\n\nclass Generator(DataStoringObject):\n    def __init__(self, start_beat, condition, on_create, request, request_clear):\n        super().__init__()\n\n        self.parent = None\n        self.scene = None\n\n        self.data = {}\n        self.start_beat = start_beat\n        self.condition = condition\n        self.on_create = on_create\n        self.request = request\n        self.request_clear = request_clear\n\n    def set_parent(self, parent):\n        self.parent = parent\n\n    def set_scene(self, scene):\n        self.scene = scene\n\n    @staticmethod\n    def combine_conditions(*cond):\n        return lambda b: all(c(b) for c in cond)\n\n    @staticmethod\n    def always():\n        return lambda b: True\n\n    @staticmethod\n    def every_n_beats(beat):\n        return lambda b: b % beat == 0\n\n    @staticmethod\n    def every_on_off(on, off):\n        return lambda b: (b % (on + off)) < on\n\n    @staticmethod\n    def every_off_on(off, on):\n        return lambda b: (b % (on + off)) >= off\n\n    @staticmethod\n    def before_n(beat):\n        return lambda b: b < beat\n\n    @staticmethod\n    def at_beat(beat):\n        return lambda b: b == beat\n\n    @staticmethod\n    def no_create():\n        return lambda g: None\n\n    @staticmethod\n    def no_request():\n        return lambda g, b: None\n"
  },
  {
    "path": "colorama/__init__.py",
    "content": "# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.\nfrom .initialise import init, deinit, reinit, colorama_text\nfrom .ansi import Fore, Back, Style, Cursor\nfrom .ansitowin32 import AnsiToWin32\n\n__version__ = '0.3.7'\n\n"
  },
  {
    "path": "colorama/ansi.py",
    "content": "# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.\n'''\nThis module generates ANSI character codes to printing colors to terminals.\nSee: http://en.wikipedia.org/wiki/ANSI_escape_code\n'''\n\nCSI = '\\033['\nOSC = '\\033]'\nBEL = '\\007'\n\n\ndef code_to_chars(code):\n    return CSI + str(code) + 'm'\n\ndef set_title(title):\n    return OSC + '2;' + title + BEL\n\ndef clear_screen(mode=2):\n    return CSI + str(mode) + 'J'\n\ndef clear_line(mode=2):\n    return CSI + str(mode) + 'K'\n\n\nclass AnsiCodes(object):\n    def __init__(self):\n        # the subclasses declare class attributes which are numbers.\n        # Upon instantiation we define instance attributes, which are the same\n        # as the class attributes but wrapped with the ANSI escape sequence\n        for name in dir(self):\n            if not name.startswith('_'):\n                value = getattr(self, name)\n                setattr(self, name, code_to_chars(value))\n\n\nclass AnsiCursor(object):\n    def UP(self, n=1):\n        return CSI + str(n) + 'A'\n    def DOWN(self, n=1):\n        return CSI + str(n) + 'B'\n    def FORWARD(self, n=1):\n        return CSI + str(n) + 'C'\n    def BACK(self, n=1):\n        return CSI + str(n) + 'D'\n    def POS(self, x=1, y=1):\n        return CSI + str(y) + ';' + str(x) + 'H'\n\n\nclass AnsiFore(AnsiCodes):\n    BLACK           = 30\n    RED             = 31\n    GREEN           = 32\n    YELLOW          = 33\n    BLUE            = 34\n    MAGENTA         = 35\n    CYAN            = 36\n    WHITE           = 37\n    RESET           = 39\n\n    # These are fairly well supported, but not part of the standard.\n    LIGHTBLACK_EX   = 90\n    LIGHTRED_EX     = 91\n    LIGHTGREEN_EX   = 92\n    LIGHTYELLOW_EX  = 93\n    LIGHTBLUE_EX    = 94\n    LIGHTMAGENTA_EX = 95\n    LIGHTCYAN_EX    = 96\n    LIGHTWHITE_EX   = 97\n\n\nclass AnsiBack(AnsiCodes):\n    BLACK           = 40\n    RED             = 41\n    GREEN           = 42\n    YELLOW          = 43\n    BLUE            = 44\n    MAGENTA         = 45\n    CYAN            = 46\n    WHITE           = 47\n    RESET           = 49\n\n    # These are fairly well supported, but not part of the standard.\n    LIGHTBLACK_EX   = 100\n    LIGHTRED_EX     = 101\n    LIGHTGREEN_EX   = 102\n    LIGHTYELLOW_EX  = 103\n    LIGHTBLUE_EX    = 104\n    LIGHTMAGENTA_EX = 105\n    LIGHTCYAN_EX    = 106\n    LIGHTWHITE_EX   = 107\n\n\nclass AnsiStyle(AnsiCodes):\n    BRIGHT    = 1\n    DIM       = 2\n    NORMAL    = 22\n    RESET_ALL = 0\n\nFore   = AnsiFore()\nBack   = AnsiBack()\nStyle  = AnsiStyle()\nCursor = AnsiCursor()\n"
  },
  {
    "path": "colorama/ansitowin32.py",
    "content": "# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.\nimport re\nimport sys\nimport os\n\nfrom .ansi import AnsiFore, AnsiBack, AnsiStyle, Style\nfrom .winterm import WinTerm, WinColor, WinStyle\nfrom .win32 import windll, winapi_test\n\n\nwinterm = None\nif windll is not None:\n    winterm = WinTerm()\n\n\ndef is_stream_closed(stream):\n    return not hasattr(stream, 'closed') or stream.closed\n\n\ndef is_a_tty(stream):\n    return hasattr(stream, 'isatty') and stream.isatty()\n\n\nclass StreamWrapper(object):\n    '''\n    Wraps a stream (such as stdout), acting as a transparent proxy for all\n    attribute access apart from method 'write()', which is delegated to our\n    Converter instance.\n    '''\n    def __init__(self, wrapped, converter):\n        # double-underscore everything to prevent clashes with names of\n        # attributes on the wrapped stream object.\n        self.__wrapped = wrapped\n        self.__convertor = converter\n\n    def __getattr__(self, name):\n        return getattr(self.__wrapped, name)\n\n    def write(self, text):\n        self.__convertor.write(text)\n\n\nclass AnsiToWin32(object):\n    '''\n    Implements a 'write()' method which, on Windows, will strip ANSI character\n    sequences from the text, and if outputting to a tty, will convert them into\n    win32 function calls.\n    '''\n    ANSI_CSI_RE = re.compile('\\001?\\033\\[((?:\\d|;)*)([a-zA-Z])\\002?')     # Control Sequence Introducer\n    ANSI_OSC_RE = re.compile('\\001?\\033\\]((?:.|;)*?)(\\x07)\\002?')         # Operating System Command\n\n    def __init__(self, wrapped, convert=None, strip=None, autoreset=False):\n        # The wrapped stream (normally sys.stdout or sys.stderr)\n        self.wrapped = wrapped\n\n        # should we reset colors to defaults after every .write()\n        self.autoreset = autoreset\n\n        # create the proxy wrapping our output stream\n        self.stream = StreamWrapper(wrapped, self)\n\n        on_windows = os.name == 'nt'\n        # We test if the WinAPI works, because even if we are on Windows\n        # we may be using a terminal that doesn't support the WinAPI\n        # (e.g. Cygwin Terminal). In this case it's up to the terminal\n        # to support the ANSI codes.\n        conversion_supported = on_windows and winapi_test()\n\n        # should we strip ANSI sequences from our output?\n        if strip is None:\n            strip = conversion_supported or (not is_stream_closed(wrapped) and not is_a_tty(wrapped))\n        self.strip = strip\n\n        # should we should convert ANSI sequences into win32 calls?\n        if convert is None:\n            convert = conversion_supported and not is_stream_closed(wrapped) and is_a_tty(wrapped)\n        self.convert = convert\n\n        # dict of ansi codes to win32 functions and parameters\n        self.win32_calls = self.get_win32_calls()\n\n        # are we wrapping stderr?\n        self.on_stderr = self.wrapped is sys.stderr\n\n    def should_wrap(self):\n        '''\n        True if this class is actually needed. If false, then the output\n        stream will not be affected, nor will win32 calls be issued, so\n        wrapping stdout is not actually required. This will generally be\n        False on non-Windows platforms, unless optional functionality like\n        autoreset has been requested using kwargs to init()\n        '''\n        return self.convert or self.strip or self.autoreset\n\n    def get_win32_calls(self):\n        if self.convert and winterm:\n            return {\n                AnsiStyle.RESET_ALL: (winterm.reset_all, ),\n                AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT),\n                AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL),\n                AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL),\n                AnsiFore.BLACK: (winterm.fore, WinColor.BLACK),\n                AnsiFore.RED: (winterm.fore, WinColor.RED),\n                AnsiFore.GREEN: (winterm.fore, WinColor.GREEN),\n                AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW),\n                AnsiFore.BLUE: (winterm.fore, WinColor.BLUE),\n                AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA),\n                AnsiFore.CYAN: (winterm.fore, WinColor.CYAN),\n                AnsiFore.WHITE: (winterm.fore, WinColor.GREY),\n                AnsiFore.RESET: (winterm.fore, ),\n                AnsiFore.LIGHTBLACK_EX: (winterm.fore, WinColor.BLACK, True),\n                AnsiFore.LIGHTRED_EX: (winterm.fore, WinColor.RED, True),\n                AnsiFore.LIGHTGREEN_EX: (winterm.fore, WinColor.GREEN, True),\n                AnsiFore.LIGHTYELLOW_EX: (winterm.fore, WinColor.YELLOW, True),\n                AnsiFore.LIGHTBLUE_EX: (winterm.fore, WinColor.BLUE, True),\n                AnsiFore.LIGHTMAGENTA_EX: (winterm.fore, WinColor.MAGENTA, True),\n                AnsiFore.LIGHTCYAN_EX: (winterm.fore, WinColor.CYAN, True),\n                AnsiFore.LIGHTWHITE_EX: (winterm.fore, WinColor.GREY, True),\n                AnsiBack.BLACK: (winterm.back, WinColor.BLACK),\n                AnsiBack.RED: (winterm.back, WinColor.RED),\n                AnsiBack.GREEN: (winterm.back, WinColor.GREEN),\n                AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW),\n                AnsiBack.BLUE: (winterm.back, WinColor.BLUE),\n                AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA),\n                AnsiBack.CYAN: (winterm.back, WinColor.CYAN),\n                AnsiBack.WHITE: (winterm.back, WinColor.GREY),\n                AnsiBack.RESET: (winterm.back, ),\n                AnsiBack.LIGHTBLACK_EX: (winterm.back, WinColor.BLACK, True),\n                AnsiBack.LIGHTRED_EX: (winterm.back, WinColor.RED, True),\n                AnsiBack.LIGHTGREEN_EX: (winterm.back, WinColor.GREEN, True),\n                AnsiBack.LIGHTYELLOW_EX: (winterm.back, WinColor.YELLOW, True),\n                AnsiBack.LIGHTBLUE_EX: (winterm.back, WinColor.BLUE, True),\n                AnsiBack.LIGHTMAGENTA_EX: (winterm.back, WinColor.MAGENTA, True),\n                AnsiBack.LIGHTCYAN_EX: (winterm.back, WinColor.CYAN, True),\n                AnsiBack.LIGHTWHITE_EX: (winterm.back, WinColor.GREY, True),\n            }\n        return dict()\n\n    def write(self, text):\n        if self.strip or self.convert:\n            self.write_and_convert(text)\n        else:\n            self.wrapped.write(text)\n            self.wrapped.flush()\n        if self.autoreset:\n            self.reset_all()\n\n\n    def reset_all(self):\n        if self.convert:\n            self.call_win32('m', (0,))\n        elif not self.strip and not is_stream_closed(self.wrapped):\n            self.wrapped.write(Style.RESET_ALL)\n\n\n    def write_and_convert(self, text):\n        '''\n        Write the given text to our wrapped stream, stripping any ANSI\n        sequences from the text, and optionally converting them into win32\n        calls.\n        '''\n        cursor = 0\n        text = self.convert_osc(text)\n        for match in self.ANSI_CSI_RE.finditer(text):\n            start, end = match.span()\n            self.write_plain_text(text, cursor, start)\n            self.convert_ansi(*match.groups())\n            cursor = end\n        self.write_plain_text(text, cursor, len(text))\n\n\n    def write_plain_text(self, text, start, end):\n        if start < end:\n            self.wrapped.write(text[start:end])\n            self.wrapped.flush()\n\n\n    def convert_ansi(self, paramstring, command):\n        if self.convert:\n            params = self.extract_params(command, paramstring)\n            self.call_win32(command, params)\n\n\n    def extract_params(self, command, paramstring):\n        if command in 'Hf':\n            params = tuple(int(p) if len(p) != 0 else 1 for p in paramstring.split(';'))\n            while len(params) < 2:\n                # defaults:\n                params = params + (1,)\n        else:\n            params = tuple(int(p) for p in paramstring.split(';') if len(p) != 0)\n            if len(params) == 0:\n                # defaults:\n                if command in 'JKm':\n                    params = (0,)\n                elif command in 'ABCD':\n                    params = (1,)\n\n        return params\n\n\n    def call_win32(self, command, params):\n        if command == 'm':\n            for param in params:\n                if param in self.win32_calls:\n                    func_args = self.win32_calls[param]\n                    func = func_args[0]\n                    args = func_args[1:]\n                    kwargs = dict(on_stderr=self.on_stderr)\n                    func(*args, **kwargs)\n        elif command in 'J':\n            winterm.erase_screen(params[0], on_stderr=self.on_stderr)\n        elif command in 'K':\n            winterm.erase_line(params[0], on_stderr=self.on_stderr)\n        elif command in 'Hf':     # cursor position - absolute\n            winterm.set_cursor_position(params, on_stderr=self.on_stderr)\n        elif command in 'ABCD':   # cursor position - relative\n            n = params[0]\n            # A - up, B - down, C - forward, D - back\n            x, y = {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}[command]\n            winterm.cursor_adjust(x, y, on_stderr=self.on_stderr)\n\n\n    def convert_osc(self, text):\n        for match in self.ANSI_OSC_RE.finditer(text):\n            start, end = match.span()\n            text = text[:start] + text[end:]\n            paramstring, command = match.groups()\n            if command in '\\x07':       # \\x07 = BEL\n                params = paramstring.split(\";\")\n                # 0 - change title and icon (we will only change title)\n                # 1 - change icon (we don't support this)\n                # 2 - change title\n                if params[0] in '02':\n                    winterm.set_title(params[1])\n        return text\n"
  },
  {
    "path": "colorama/initialise.py",
    "content": "# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.\nimport atexit\nimport contextlib\nimport sys\n\nfrom .ansitowin32 import AnsiToWin32\n\n\norig_stdout = None\norig_stderr = None\n\nwrapped_stdout = None\nwrapped_stderr = None\n\natexit_done = False\n\n\ndef reset_all():\n    if AnsiToWin32 is not None:    # Issue #74: objects might become None at exit\n        AnsiToWin32(orig_stdout).reset_all()\n\n\ndef init(autoreset=False, convert=None, strip=None, wrap=True):\n\n    if not wrap and any([autoreset, convert, strip]):\n        raise ValueError('wrap=False conflicts with any other arg=True')\n\n    global wrapped_stdout, wrapped_stderr\n    global orig_stdout, orig_stderr\n\n    orig_stdout = sys.stdout\n    orig_stderr = sys.stderr\n\n    if sys.stdout is None:\n        wrapped_stdout = None\n    else:\n        sys.stdout = wrapped_stdout = \\\n            wrap_stream(orig_stdout, convert, strip, autoreset, wrap)\n    if sys.stderr is None:\n        wrapped_stderr = None\n    else:\n        sys.stderr = wrapped_stderr = \\\n            wrap_stream(orig_stderr, convert, strip, autoreset, wrap)\n\n    global atexit_done\n    if not atexit_done:\n        atexit.register(reset_all)\n        atexit_done = True\n\n\ndef deinit():\n    if orig_stdout is not None:\n        sys.stdout = orig_stdout\n    if orig_stderr is not None:\n        sys.stderr = orig_stderr\n\n\n@contextlib.contextmanager\ndef colorama_text(*args, **kwargs):\n    init(*args, **kwargs)\n    try:\n        yield\n    finally:\n        deinit()\n\n\ndef reinit():\n    if wrapped_stdout is not None:\n        sys.stdout = wrapped_stdout\n    if wrapped_stderr is not None:\n        sys.stderr = wrapped_stderr\n\n\ndef wrap_stream(stream, convert, strip, autoreset, wrap):\n    if wrap:\n        wrapper = AnsiToWin32(stream,\n            convert=convert, strip=strip, autoreset=autoreset)\n        if wrapper.should_wrap():\n            stream = wrapper.stream\n    return stream\n\n\n"
  },
  {
    "path": "colorama/win32.py",
    "content": "# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.\n\n# from winbase.h\nSTDOUT = -11\nSTDERR = -12\n\ntry:\n    import ctypes\n    from ctypes import LibraryLoader\n    windll = LibraryLoader(ctypes.WinDLL)\n    from ctypes import wintypes\nexcept (AttributeError, ImportError):\n    windll = None\n    SetConsoleTextAttribute = lambda *_: None\n    winapi_test = lambda *_: None\nelse:\n    from ctypes import byref, Structure, c_char, POINTER\n\n    COORD = wintypes._COORD\n\n    class CONSOLE_SCREEN_BUFFER_INFO(Structure):\n        \"\"\"struct in wincon.h.\"\"\"\n        _fields_ = [\n            (\"dwSize\", COORD),\n            (\"dwCursorPosition\", COORD),\n            (\"wAttributes\", wintypes.WORD),\n            (\"srWindow\", wintypes.SMALL_RECT),\n            (\"dwMaximumWindowSize\", COORD),\n        ]\n        def __str__(self):\n            return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % (\n                self.dwSize.Y, self.dwSize.X\n                , self.dwCursorPosition.Y, self.dwCursorPosition.X\n                , self.wAttributes\n                , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right\n                , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X\n            )\n\n    _GetStdHandle = windll.kernel32.GetStdHandle\n    _GetStdHandle.argtypes = [\n        wintypes.DWORD,\n    ]\n    _GetStdHandle.restype = wintypes.HANDLE\n\n    _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo\n    _GetConsoleScreenBufferInfo.argtypes = [\n        wintypes.HANDLE,\n        POINTER(CONSOLE_SCREEN_BUFFER_INFO),\n    ]\n    _GetConsoleScreenBufferInfo.restype = wintypes.BOOL\n\n    _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute\n    _SetConsoleTextAttribute.argtypes = [\n        wintypes.HANDLE,\n        wintypes.WORD,\n    ]\n    _SetConsoleTextAttribute.restype = wintypes.BOOL\n\n    _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition\n    _SetConsoleCursorPosition.argtypes = [\n        wintypes.HANDLE,\n        COORD,\n    ]\n    _SetConsoleCursorPosition.restype = wintypes.BOOL\n\n    _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA\n    _FillConsoleOutputCharacterA.argtypes = [\n        wintypes.HANDLE,\n        c_char,\n        wintypes.DWORD,\n        COORD,\n        POINTER(wintypes.DWORD),\n    ]\n    _FillConsoleOutputCharacterA.restype = wintypes.BOOL\n\n    _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute\n    _FillConsoleOutputAttribute.argtypes = [\n        wintypes.HANDLE,\n        wintypes.WORD,\n        wintypes.DWORD,\n        COORD,\n        POINTER(wintypes.DWORD),\n    ]\n    _FillConsoleOutputAttribute.restype = wintypes.BOOL\n\n    _SetConsoleTitleW = windll.kernel32.SetConsoleTitleA\n    _SetConsoleTitleW.argtypes = [\n        wintypes.LPCSTR\n    ]\n    _SetConsoleTitleW.restype = wintypes.BOOL\n\n    handles = {\n        STDOUT: _GetStdHandle(STDOUT),\n        STDERR: _GetStdHandle(STDERR),\n    }\n\n    def winapi_test():\n        handle = handles[STDOUT]\n        csbi = CONSOLE_SCREEN_BUFFER_INFO()\n        success = _GetConsoleScreenBufferInfo(\n            handle, byref(csbi))\n        return bool(success)\n\n    def GetConsoleScreenBufferInfo(stream_id=STDOUT):\n        handle = handles[stream_id]\n        csbi = CONSOLE_SCREEN_BUFFER_INFO()\n        success = _GetConsoleScreenBufferInfo(\n            handle, byref(csbi))\n        return csbi\n\n    def SetConsoleTextAttribute(stream_id, attrs):\n        handle = handles[stream_id]\n        return _SetConsoleTextAttribute(handle, attrs)\n\n    def SetConsoleCursorPosition(stream_id, position, adjust=True):\n        position = COORD(*position)\n        # If the position is out of range, do nothing.\n        if position.Y <= 0 or position.X <= 0:\n            return\n        # Adjust for Windows' SetConsoleCursorPosition:\n        #    1. being 0-based, while ANSI is 1-based.\n        #    2. expecting (x,y), while ANSI uses (y,x).\n        adjusted_position = COORD(position.Y - 1, position.X - 1)\n        if adjust:\n            # Adjust for viewport's scroll position\n            sr = GetConsoleScreenBufferInfo(STDOUT).srWindow\n            adjusted_position.Y += sr.Top\n            adjusted_position.X += sr.Left\n        # Resume normal processing\n        handle = handles[stream_id]\n        return _SetConsoleCursorPosition(handle, adjusted_position)\n\n    def FillConsoleOutputCharacter(stream_id, char, length, start):\n        handle = handles[stream_id]\n        char = c_char(char.encode())\n        length = wintypes.DWORD(length)\n        num_written = wintypes.DWORD(0)\n        # Note that this is hard-coded for ANSI (vs wide) bytes.\n        success = _FillConsoleOutputCharacterA(\n            handle, char, length, start, byref(num_written))\n        return num_written.value\n\n    def FillConsoleOutputAttribute(stream_id, attr, length, start):\n        ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )'''\n        handle = handles[stream_id]\n        attribute = wintypes.WORD(attr)\n        length = wintypes.DWORD(length)\n        num_written = wintypes.DWORD(0)\n        # Note that this is hard-coded for ANSI (vs wide) bytes.\n        return _FillConsoleOutputAttribute(\n            handle, attribute, length, start, byref(num_written))\n\n    def SetConsoleTitle(title):\n        return _SetConsoleTitleW(title)\n"
  },
  {
    "path": "colorama/winterm.py",
    "content": "# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.\nfrom . import win32\n\n\n# from wincon.h\nclass WinColor(object):\n    BLACK   = 0\n    BLUE    = 1\n    GREEN   = 2\n    CYAN    = 3\n    RED     = 4\n    MAGENTA = 5\n    YELLOW  = 6\n    GREY    = 7\n\n# from wincon.h\nclass WinStyle(object):\n    NORMAL              = 0x00 # dim text, dim background\n    BRIGHT              = 0x08 # bright text, dim background\n    BRIGHT_BACKGROUND   = 0x80 # dim text, bright background\n\nclass WinTerm(object):\n\n    def __init__(self):\n        self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes\n        self.set_attrs(self._default)\n        self._default_fore = self._fore\n        self._default_back = self._back\n        self._default_style = self._style\n        # In order to emulate LIGHT_EX in windows, we borrow the BRIGHT style.\n        # So that LIGHT_EX colors and BRIGHT style do not clobber each other,\n        # we track them separately, since LIGHT_EX is overwritten by Fore/Back\n        # and BRIGHT is overwritten by Style codes.\n        self._light = 0\n\n    def get_attrs(self):\n        return self._fore + self._back * 16 + (self._style | self._light)\n\n    def set_attrs(self, value):\n        self._fore = value & 7\n        self._back = (value >> 4) & 7\n        self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND)\n\n    def reset_all(self, on_stderr=None):\n        self.set_attrs(self._default)\n        self.set_console(attrs=self._default)\n\n    def fore(self, fore=None, light=False, on_stderr=False):\n        if fore is None:\n            fore = self._default_fore\n        self._fore = fore\n        # Emulate LIGHT_EX with BRIGHT Style\n        if light:\n            self._light |= WinStyle.BRIGHT\n        else:\n            self._light &= ~WinStyle.BRIGHT\n        self.set_console(on_stderr=on_stderr)\n\n    def back(self, back=None, light=False, on_stderr=False):\n        if back is None:\n            back = self._default_back\n        self._back = back\n        # Emulate LIGHT_EX with BRIGHT_BACKGROUND Style\n        if light:\n            self._light |= WinStyle.BRIGHT_BACKGROUND\n        else:\n            self._light &= ~WinStyle.BRIGHT_BACKGROUND\n        self.set_console(on_stderr=on_stderr)\n\n    def style(self, style=None, on_stderr=False):\n        if style is None:\n            style = self._default_style\n        self._style = style\n        self.set_console(on_stderr=on_stderr)\n\n    def set_console(self, attrs=None, on_stderr=False):\n        if attrs is None:\n            attrs = self.get_attrs()\n        handle = win32.STDOUT\n        if on_stderr:\n            handle = win32.STDERR\n        win32.SetConsoleTextAttribute(handle, attrs)\n\n    def get_position(self, handle):\n        position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition\n        # Because Windows coordinates are 0-based,\n        # and win32.SetConsoleCursorPosition expects 1-based.\n        position.X += 1\n        position.Y += 1\n        return position\n\n    def set_cursor_position(self, position=None, on_stderr=False):\n        if position is None:\n            # I'm not currently tracking the position, so there is no default.\n            # position = self.get_position()\n            return\n        handle = win32.STDOUT\n        if on_stderr:\n            handle = win32.STDERR\n        win32.SetConsoleCursorPosition(handle, position)\n\n    def cursor_adjust(self, x, y, on_stderr=False):\n        handle = win32.STDOUT\n        if on_stderr:\n            handle = win32.STDERR\n        position = self.get_position(handle)\n        adjusted_position = (position.Y + y, position.X + x)\n        win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False)\n\n    def erase_screen(self, mode=0, on_stderr=False):\n        # 0 should clear from the cursor to the end of the screen.\n        # 1 should clear from the cursor to the beginning of the screen.\n        # 2 should clear the entire screen, and move cursor to (1,1)\n        handle = win32.STDOUT\n        if on_stderr:\n            handle = win32.STDERR\n        csbi = win32.GetConsoleScreenBufferInfo(handle)\n        # get the number of character cells in the current buffer\n        cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y\n        # get number of character cells before current cursor position\n        cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X\n        if mode == 0:\n            from_coord = csbi.dwCursorPosition\n            cells_to_erase = cells_in_screen - cells_before_cursor\n        if mode == 1:\n            from_coord = win32.COORD(0, 0)\n            cells_to_erase = cells_before_cursor\n        elif mode == 2:\n            from_coord = win32.COORD(0, 0)\n            cells_to_erase = cells_in_screen\n        # fill the entire screen with blanks\n        win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)\n        # now set the buffer's attributes accordingly\n        win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)\n        if mode == 2:\n            # put the cursor where needed\n            win32.SetConsoleCursorPosition(handle, (1, 1))\n\n    def erase_line(self, mode=0, on_stderr=False):\n        # 0 should clear from the cursor to the end of the line.\n        # 1 should clear from the cursor to the beginning of the line.\n        # 2 should clear the entire line.\n        handle = win32.STDOUT\n        if on_stderr:\n            handle = win32.STDERR\n        csbi = win32.GetConsoleScreenBufferInfo(handle)\n        if mode == 0:\n            from_coord = csbi.dwCursorPosition\n            cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X\n        if mode == 1:\n            from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)\n            cells_to_erase = csbi.dwCursorPosition.X\n        elif mode == 2:\n            from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)\n            cells_to_erase = csbi.dwSize.X\n        # fill the entire screen with blanks\n        win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)\n        # now set the buffer's attributes accordingly\n        win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)\n\n    def set_title(self, title):\n        win32.SetConsoleTitle(title)\n"
  },
  {
    "path": "credits.py",
    "content": "from animation_functions import debug_info\nfrom CLIRender.classes import enable_ansi\nfrom colorama import Fore, Style\n\nimport keyboard\nimport time\nimport os\nimport random\nfrom just_playback import Playback\n\nfrom animation_scenes import all_scenes, canvas\nfrom string_defs import data_strings\n\nimport animator as am\n\nenable_ansi()\n# canvas.render_blank()\ndelay = 60.0 / 179.0 / 8.0\nbeat = -60\noffset = 5.492\n# print((delay * 980) + offset)\nskip_by = 0\n\ndebug = False\nlast_frames = []\n\n\ndef skip_beats(ctr, amount, next_debug):\n    global beat\n    global skip_by\n\n    ctr.cur_beat += amount\n    beat += amount\n\n    if debug:\n        controller.events[next_debug] = (am.Event(next_debug, am.Event.layer_scene(\"debug_counter\")),)\n\n    skip_by = offset + (delay * amount)\n\n\ncounter = am.Scene(\n    \"debug_counter\",\n    (\n        am.Generator(\n            0, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: debug_info(canvas, g, b, last_frames),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\nocean_events = (\n    am.Event(60, lambda c: c.set_generator_data(\n        \"ocean_b\", 1, \"text\", data_strings[\"ocean_b_0\"]\n    )),\n    am.Event(310, lambda c: c.set_generator_data(\n        \"ocean_b\", 1,\n        \"text\", \"[##CLEAR|60;6\"\n    )),\n    am.Event(310, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 3\n    )),\n    am.Event(310, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_col\", Style.NORMAL + Fore.BLUE\n    )),\n    am.Event(312, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_col\", Style.BRIGHT + Fore.BLUE\n    )),\n    am.Event(314, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_col\", Style.NORMAL + Fore.BLUE\n    )),\n    am.Event(320, lambda c: c.set_generator_data(\n        \"ocean_b\", 1, \"text\", data_strings[\"ocean_b_1\"], \"offset\", 0\n    )),\n    am.Event(590, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_col\", Style.BRIGHT + Fore.BLACK\n    )),\n    am.Event(646, lambda c: c.set_generator_data(\n        \"ocean_b\", 1,\n        \"text\", \"[##CLEAR|60;8\"\n    )),\n    am.Event(652, lambda c: c.set_generator_data(\n        \"ocean_b\", 1,\n        \"text\", data_strings[\"ocean_b_2\"], \"offset\", 0\n    )),\n    am.Event(656, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_col\", Style.NORMAL + Fore.YELLOW\n    )),\n    am.Event(666, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_col\", Style.BRIGHT + Fore.YELLOW\n    )),\n    am.Event(666, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 6\n    )),\n    am.Event(850, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_col\", Style.NORMAL + Fore.YELLOW\n    )),\n    am.Event(999, lambda c: c.set_generator_data(\n        \"ocean_b\", 1,\n        \"text\", \"[##CLEAR|60;10\"\n    )),\n    am.Event(1000, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_col\", Style.BRIGHT + Fore.BLACK\n    )),\n    am.Event(1000, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 13\n    )),\n    am.Event(1000, lambda c: c.set_generator_data(\n        \"ocean_b\", 1,\n        \"text\", data_strings[\"ocean_b_3\"],\n        \"offset\", 0\n    )),\n    am.Event(1044, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 102\n    )),\n    am.Event(1048, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 230\n    )),\n    am.Event(1052, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 500\n    )),\n    am.Event(1056, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 760\n    )),\n    am.Event(1060, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 1600\n    )),\n    am.Event(1064, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 2500\n    ))\n)\n\n\nocean2_events = (\n    am.Event(3896, lambda c: c.set_generator_data(\n            \"ocean_c\", 1, \"text\", data_strings[\"ocean_c_0\"]\n        )\n    ),\n    am.Event(3896, lambda c: c.set_generator_data(\n            \"ocean_c\", 2, \"text\", data_strings[\"ocean_c_1\"]\n        )\n    ),\n\n    am.Event(3896, lambda c: c.set_generator_data(\n            \"ocean_c\", 3, \"text\", data_strings[\"ocean_c_2\"]\n        )\n    ),\n\n    am.Event(3896, lambda c: c.set_generator_data(\n            \"ocean_c\", 4, \"text\", data_strings[\"ocean_c_3\"]\n        )\n    ),\n\n    am.Event(4400, lambda c: c.set_generator_data(\n        \"ocean_c\", 1,\n        \"text\", \"[##CLEAR|60;10\"\n    )),\n\n    *(\n        am.Event(4401 + i * 2, lambda c: c.set_generator_data(\n            \"ocean_c\", 0, \"ocean_col\", random.choice((Style.BRIGHT, Style.NORMAL)) + Fore.BLACK\n        )) for i in range(5)\n    ),\n\n    am.Event(4411, lambda c: c.set_generator_data(\n        \"ocean_c\", 0, \"ocean_col\", Style.NORMAL + Fore.BLACK\n    )),\n)\n\n\ncontroller = am.SceneManager((*all_scenes, counter), (\n    am.Event(0, am.Event.swap_scene(\"wipe\")),\n    # am.Event(1, am.Event.layer_scene(\"debug_counter\")),\n    am.Event(58, am.Event.swap_scene(\"clear\")),\n    am.Event(60, am.Event.swap_scene(\"ocean_b\")),\n    # am.Event(60, am.Event.layer_scene(\"typewrite\")),\n    *ocean_events,\n    am.Event(1079, am.Event.swap_scene(\"clear\")),\n    am.Event(1080, am.Event.swap_scene(\"beats\")),\n    am.Event(1080, am.Event.layer_scene(\"title\")),\n    am.Event(1080, am.Event.swap_scene(\"beats\")),\n    am.Event(1336, am.Event.swap_scene(\"beats_lr\")),\n    am.Event(1844, am.Event.remove_scene(\"title\")),\n    am.Event(1848, am.Event.swap_scene(\"funding\")),\n    am.Event(1848, am.Event.layer_scene(\"dates\")),\n    am.Event(1848, am.Event.layer_scene(\"weather\")),\n    am.Event(1848, lambda c: c.set_data(\n        \"history\", [], \"refresh\", True\n    )),\n\n    am.Event(1848, lambda c: c.set_generator_data(\n        \"funding\", 0, \"text\", data_strings[\"funding_0\"]\n    )),\n\n    am.Event(2348, lambda c: c.set_generator_data(\n        \"funding\", 0, \"text\", (('',), ('',))\n    )),\n\n    am.Event(2348, lambda c: c.set_data(\n        \"history\", [], \"refresh\", True\n    )),\n\n    am.Event(2352, lambda c: c.set_generator_data(\n        \"funding\", 0, \"text\", data_strings[\"funding_1\"], \"offset\", 0, \"lineno\", 0\n    )),\n\n    am.Event(2976, am.Event.remove_scene(\"dates\")),\n    am.Event(2976, am.Event.remove_scene(\"weather\")),\n\n    am.Event(2976, am.Event.swap_scene(\"clear_wipe\")),\n    am.Event(3007, am.Event.swap_scene(\"clear\")),\n\n    # idk what to do here. some sort of bootup sequence?\n    am.Event(3132, am.Event.swap_scene(\"loadingbar\")),\n    am.Event(3132, lambda c: c.set_generator_data(\n        \"loadingbar\", 6, \"text\", data_strings[\"funding_2\"]\n    )),\n\n    am.Event(3376, am.Event.swap_scene(\"ocean_d\")),\n    am.Event(3380, am.Event.swap_scene(\"clear\")),\n    am.Event(3380, am.Event.swap_scene(\"fastload\")),\n    am.Event(3388, am.Event.swap_scene(\"clear\")),\n\n    # Crazy part. Go wild\n    am.Event(3390, am.Event.swap_scene(\"error\")),\n    am.Event(3390, am.Event.layer_scene(\"fundingx2\")),\n    am.Event(3390, lambda c: c.set_data(\n        \"history\", [], \"refresh\", True\n    )),\n\n    am.Event(3390, lambda c: c.set_generator_data(\n        \"fundingx2\", 0,\n        \"text\", data_strings[\"fundingx2_0\"]\n    )),\n\n    am.Event(3390, lambda c: c.set_generator_data(\n        \"fundingx2\", 1,\n        \"text\", data_strings[\"fundingx2_1\"]\n    )),\n\n    am.Event(3390, lambda c: c.set_generator_data(\n        \"fundingx2\", 3,\n        \"text\", data_strings[\"fundingx2_2\"]\n    )),\n\n    am.Event(3895, am.Event.remove_scene(\"fundingx2\")),\n    am.Event(3895, am.Event.swap_scene(\"clear\")),\n    am.Event(3896, am.Event.swap_scene(\"ocean_c\")),\n    *ocean2_events,\n\n    # remember to clean the ocean_c events up, or it will stay in the screen. At frame 4413.\n    am.Event(4413, am.Event.swap_scene(\"clear\")),\n    am.Event(4460, am.Event.swap_scene(\"accesspoints\")),\n    am.Event(4460, am.Event.layer_scene(\"fdg_single\")),\n    am.Event(4534, lambda c: c.set_generator_data(\n        \"fdg_single\", 1, \"text\", data_strings[\"fdg_single_0\"]\n    )),\n\n    am.Event(5500, am.Event.remove_scene(\"fdg_single\")),\n    am.Event(5500, am.Event.swap_scene(\"clear\")),\n    am.Event(5500, am.Event.swap_scene(\"beats\")),\n    am.Event(5500, am.Event.layer_scene(\"fdg_down\")),\n    am.Event(5788, lambda c: c.set_generator_data(\n        \"fdg_down\", 0, \"text\", data_strings[\"fdg_down_0\"]\n    )),\n\n    am.Event(5916, lambda c: c.set_generator_data(\n        \"fdg_down\", 0,\n        \"text\", data_strings[\"fdg_down_1\"], \"lineno\", 0, \"offset\", 0\n    )),\n\n    am.Event(5916, lambda c: c.set_generator_data(\n        \"fdg_down\", 1,\n        \"text\", data_strings[\"fdg_down_2\"]\n    )),\n\n    am.Event(6270, am.Event.swap_scene(\"beats_lr\")),\n\n    am.Event(6508, am.Event.remove_scene(\"fdg_down\")),\n    # am.Event(6508, am.Event.remove_scene(\"debug_counter\")),\n\n    am.Event(6508, am.Event.swap_scene(\"clear\"))\n))\n\n\n# class MixerWrapper:\n#     def __init__(self):\n#         self.is_paused = False\n#\n#     def toggle_music(self):\n#         if self.is_paused:\n#             pygame.mixer.music.unpause()\n#             self.is_paused = False\n#         else:\n#             pygame.mixer.music.pause()\n#             self.is_paused = True\n#\n#\n# ffwing = MixerWrapper()\n\npaused_this_frame = False\nff_this_frame = False\n\nfilename = \"media/credits.wav\"\n\nplayback = Playback()\nplayback.load_file(filename)\n\n# Clear the console before showing the skip menu\nif os.name == \"nt\":\n    os.system(\"cls\")\nelif os.name == \"posix\":\n    os.system(\"clear\")\nelse:\n    print(\"\\033[2J\")\n\nprint(\"\\033[1;1Hskips\\n\\n1 | start\\n2 | title\\n3 | funding\\n4 | loading\\n5 | break\\n6 | final\")\n\n# Skips forward to the title scene\ntime_menu = time.time()\nwhile time.time() - 2 < time_menu:\n    if keyboard.is_pressed(\"1\"):\n        time_menu = 99999999999999999999\n        break\n    elif keyboard.is_pressed(\"2\"):\n        skip_beats(controller, 1000, 1081)\n        time_menu = 99999999999999999999\n        break\n    elif keyboard.is_pressed(\"3\"):\n        skip_beats(controller, 1770, 1851)\n        controller.events[1849] = am.Event(1849, am.Event.layer_scene(\"redraw_ui\")),\n        controller.events[1860] = am.Event(1860, am.Event.remove_scene(\"redraw_ui\")),\n        time_menu = 99999999999999999999\n        break\n    elif keyboard.is_pressed(\"4\"):\n        skip_beats(controller, 3040, 3133)\n        time_menu = 99999999999999999999\n        break\n    elif keyboard.is_pressed(\"5\"):\n        skip_beats(controller, 3780, 3898)\n        time_menu = 99999999999999999999\n        break\n    elif keyboard.is_pressed(\"6\"):\n        skip_beats(controller, 5420, 5501)\n        time_menu = 99999999999999999999\n        break\n\n    time.sleep(0.01)\n\nif os.name == \"nt\":\n    os.system(\"cls\")\nelif os.name == \"posix\":\n    os.system(\"clear\")\nelse:\n    print(\"\\033[2J\")\n\n# wave_obj = sa.WaveObject.from_wave_file(filename)\n# play_obj = wave_obj.play()\nplayback.play()\nplayback.seek(skip_by)\n\ntime_start = time.time()\nlast_update = time.time()\nprev_pos = 0\n\nwhile playback.active:\n    # (17.06.21) might have broken, i used a -1 beat offset here to try and sync up everything better\n    # since i originally used 1-indexed beats\n    #\n    # (24.06.21) update chat it didnt break\n\n    # next_beat = (time.time() - time_start - offset) > ((beat - 1) * delay)\n    next_beat = (playback.curr_pos - offset) > ((beat - 1) * delay)\n    need_update = time.time() - (1/30) > last_update\n    # print(pygame.mixer.music.get_pos())\n\n    if next_beat:\n        controller.request_next()\n        canvas.render_all()\n        last_frames.append(time.time())\n        if len(last_frames) > 10:\n            last_frames.pop(0)\n\n        beat += 1\n\n    if need_update:\n        if keyboard.is_pressed(\"p\"):\n            if not paused_this_frame:\n                if playback.paused:\n                    playback.resume()\n                else:\n                    playback.pause()\n\n                paused_this_frame = True\n        else:\n            paused_this_frame = False\n\n        if keyboard.is_pressed(\",\"):\n            if not ff_this_frame:\n                ff_this_frame = True\n            else:\n                playback.seek(playback.curr_pos + delay * 3)\n\n        if keyboard.is_pressed(\".\"):\n            if not ff_this_frame:\n                ff_this_frame = True\n            else:\n                playback.seek(playback.curr_pos + delay * 7)\n\n        if keyboard.is_pressed(\"/\"):\n            if not ff_this_frame:\n                ff_this_frame = True\n            else:\n                playback.seek(playback.curr_pos + delay * 15)\n        # else:\n        #     # print(\"n\", ff_this_frame, pygame.mixer.music.get_pos() + prev_pos, prev_pos)\n        #     if ff_this_frame:\n        #         # print(\"unpausing now\")\n        #         ff_this_frame = False\n        #         ffwing.toggle_music()\n\n        last_update = time.time()\n\n# Add a final clear at the end to prevent the last frame from sticking around when the program ends.\nif os.name == \"nt\":\n    os.system(\"cls\")\nelif os.name == \"posix\":\n    os.system(\"clear\")\nelse:\n    print(\"\\033[2J\")"
  },
  {
    "path": "credits_pynput.py",
    "content": "from animation_functions import debug_info\nfrom CLIRender.classes import enable_ansi\nfrom colorama import Fore, Style\n\nfrom pynput import keyboard\nimport time\nimport os\nimport random\nfrom just_playback import Playback\n\nfrom animation_scenes import all_scenes, canvas\nfrom string_defs import data_strings\n\nimport animator as am\n\nenable_ansi()\n# canvas.render_blank()\ndelay = 60.0 / 179.0 / 8.0\nbeat = -60\noffset = 5.492\n# print((delay * 980) + offset)\nskip_by = 0\n\ndebug = False\nlast_frames = []\n\n\ndef skip_beats(ctr, amount, next_debug):\n    global beat\n    global skip_by\n\n    ctr.cur_beat += amount\n    beat += amount\n\n    if debug:\n        controller.events[next_debug] = (am.Event(next_debug, am.Event.layer_scene(\"debug_counter\")),)\n\n    skip_by = offset + (delay * amount)\n\n\ncounter = am.Scene(\n    \"debug_counter\",\n    (\n        am.Generator(\n            0, am.Generator.always(),\n            am.Generator.no_create(),\n            lambda g, b: debug_info(canvas, g, b, last_frames),\n            am.Generator.no_request()\n        ),\n    )\n)\n\n\nocean_events = (\n    am.Event(60, lambda c: c.set_generator_data(\n        \"ocean_b\", 1, \"text\", data_strings[\"ocean_b_0\"]\n    )),\n    am.Event(310, lambda c: c.set_generator_data(\n        \"ocean_b\", 1,\n        \"text\", \"[##CLEAR|60;6\"\n    )),\n    am.Event(310, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 3\n    )),\n    am.Event(310, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_col\", Style.NORMAL + Fore.BLUE\n    )),\n    am.Event(312, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_col\", Style.BRIGHT + Fore.BLUE\n    )),\n    am.Event(314, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_col\", Style.NORMAL + Fore.BLUE\n    )),\n    am.Event(320, lambda c: c.set_generator_data(\n        \"ocean_b\", 1, \"text\", data_strings[\"ocean_b_1\"], \"offset\", 0\n    )),\n    am.Event(590, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_col\", Style.BRIGHT + Fore.BLACK\n    )),\n    am.Event(646, lambda c: c.set_generator_data(\n        \"ocean_b\", 1,\n        \"text\", \"[##CLEAR|60;8\"\n    )),\n    am.Event(652, lambda c: c.set_generator_data(\n        \"ocean_b\", 1,\n        \"text\", data_strings[\"ocean_b_2\"], \"offset\", 0\n    )),\n    am.Event(656, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_col\", Style.NORMAL + Fore.YELLOW\n    )),\n    am.Event(666, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_col\", Style.BRIGHT + Fore.YELLOW\n    )),\n    am.Event(666, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 6\n    )),\n    am.Event(850, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_col\", Style.NORMAL + Fore.YELLOW\n    )),\n    am.Event(999, lambda c: c.set_generator_data(\n        \"ocean_b\", 1,\n        \"text\", \"[##CLEAR|60;10\"\n    )),\n    am.Event(1000, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_col\", Style.BRIGHT + Fore.BLACK\n    )),\n    am.Event(1000, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 13\n    )),\n    am.Event(1000, lambda c: c.set_generator_data(\n        \"ocean_b\", 1,\n        \"text\", data_strings[\"ocean_b_3\"],\n        \"offset\", 0\n    )),\n    am.Event(1044, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 102\n    )),\n    am.Event(1048, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 230\n    )),\n    am.Event(1052, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 500\n    )),\n    am.Event(1056, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 760\n    )),\n    am.Event(1060, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 1600\n    )),\n    am.Event(1064, lambda c: c.set_generator_data(\n        \"ocean_b\", 0, \"ocean_glitch\", 2500\n    ))\n)\n\n\nocean2_events = (\n    am.Event(3896, lambda c: c.set_generator_data(\n            \"ocean_c\", 1, \"text\", data_strings[\"ocean_c_0\"]\n        )\n    ),\n    am.Event(3896, lambda c: c.set_generator_data(\n            \"ocean_c\", 2, \"text\", data_strings[\"ocean_c_1\"]\n        )\n    ),\n\n    am.Event(3896, lambda c: c.set_generator_data(\n            \"ocean_c\", 3, \"text\", data_strings[\"ocean_c_2\"]\n        )\n    ),\n\n    am.Event(3896, lambda c: c.set_generator_data(\n            \"ocean_c\", 4, \"text\", data_strings[\"ocean_c_3\"]\n        )\n    ),\n\n    am.Event(4400, lambda c: c.set_generator_data(\n        \"ocean_c\", 1,\n        \"text\", \"[##CLEAR|60;10\"\n    )),\n\n    *(\n        am.Event(4401 + i * 2, lambda c: c.set_generator_data(\n            \"ocean_c\", 0, \"ocean_col\", random.choice((Style.BRIGHT, Style.NORMAL)) + Fore.BLACK\n        )) for i in range(5)\n    ),\n\n    am.Event(4411, lambda c: c.set_generator_data(\n        \"ocean_c\", 0, \"ocean_col\", Style.NORMAL + Fore.BLACK\n    )),\n)\n\n\ncontroller = am.SceneManager((*all_scenes, counter), (\n    am.Event(0, am.Event.swap_scene(\"wipe\")),\n    # am.Event(1, am.Event.layer_scene(\"debug_counter\")),\n    am.Event(58, am.Event.swap_scene(\"clear\")),\n    am.Event(60, am.Event.swap_scene(\"ocean_b\")),\n    # am.Event(60, am.Event.layer_scene(\"typewrite\")),\n    *ocean_events,\n    am.Event(1079, am.Event.swap_scene(\"clear\")),\n    am.Event(1080, am.Event.swap_scene(\"beats\")),\n    am.Event(1080, am.Event.layer_scene(\"title\")),\n    am.Event(1080, am.Event.swap_scene(\"beats\")),\n    am.Event(1336, am.Event.swap_scene(\"beats_lr\")),\n    am.Event(1844, am.Event.remove_scene(\"title\")),\n    am.Event(1848, am.Event.swap_scene(\"funding\")),\n    am.Event(1848, am.Event.layer_scene(\"dates\")),\n    am.Event(1848, am.Event.layer_scene(\"weather\")),\n    am.Event(1848, lambda c: c.set_data(\n        \"history\", [], \"refresh\", True\n    )),\n\n    am.Event(1848, lambda c: c.set_generator_data(\n        \"funding\", 0, \"text\", data_strings[\"funding_0\"]\n    )),\n\n    am.Event(2348, lambda c: c.set_generator_data(\n        \"funding\", 0, \"text\", (('',), ('',))\n    )),\n\n    am.Event(2348, lambda c: c.set_data(\n        \"history\", [], \"refresh\", True\n    )),\n\n    am.Event(2352, lambda c: c.set_generator_data(\n        \"funding\", 0, \"text\", data_strings[\"funding_1\"], \"offset\", 0, \"lineno\", 0\n    )),\n\n    am.Event(2976, am.Event.remove_scene(\"dates\")),\n    am.Event(2976, am.Event.remove_scene(\"weather\")),\n\n    am.Event(2976, am.Event.swap_scene(\"clear_wipe\")),\n    am.Event(3007, am.Event.swap_scene(\"clear\")),\n\n    # idk what to do here. some sort of bootup sequence?\n    am.Event(3132, am.Event.swap_scene(\"loadingbar\")),\n    am.Event(3132, lambda c: c.set_generator_data(\n        \"loadingbar\", 6, \"text\", data_strings[\"funding_2\"]\n    )),\n\n    am.Event(3376, am.Event.swap_scene(\"ocean_d\")),\n    am.Event(3380, am.Event.swap_scene(\"clear\")),\n    am.Event(3380, am.Event.swap_scene(\"fastload\")),\n    am.Event(3388, am.Event.swap_scene(\"clear\")),\n\n    # Crazy part. Go wild\n    am.Event(3390, am.Event.swap_scene(\"error\")),\n    am.Event(3390, am.Event.layer_scene(\"fundingx2\")),\n    am.Event(3390, lambda c: c.set_data(\n        \"history\", [], \"refresh\", True\n    )),\n\n    am.Event(3390, lambda c: c.set_generator_data(\n        \"fundingx2\", 0,\n        \"text\", data_strings[\"fundingx2_0\"]\n    )),\n\n    am.Event(3390, lambda c: c.set_generator_data(\n        \"fundingx2\", 1,\n        \"text\", data_strings[\"fundingx2_1\"]\n    )),\n\n    am.Event(3390, lambda c: c.set_generator_data(\n        \"fundingx2\", 3,\n        \"text\", data_strings[\"fundingx2_2\"]\n    )),\n\n    am.Event(3895, am.Event.remove_scene(\"fundingx2\")),\n    am.Event(3895, am.Event.swap_scene(\"clear\")),\n    am.Event(3896, am.Event.swap_scene(\"ocean_c\")),\n    *ocean2_events,\n    \n    # remember to clean the ocean_c events up, or it will stay in the screen. At frame 4413.\n    am.Event(4413, am.Event.swap_scene(\"clear\")),\n    am.Event(4460, am.Event.swap_scene(\"accesspoints\")),\n    am.Event(4460, am.Event.layer_scene(\"fdg_single\")),\n    am.Event(4534, lambda c: c.set_generator_data(\n        \"fdg_single\", 1, \"text\", data_strings[\"fdg_single_0\"]\n    )),\n\n    am.Event(5500, am.Event.remove_scene(\"fdg_single\")),\n    am.Event(5500, am.Event.swap_scene(\"clear\")),\n    am.Event(5500, am.Event.swap_scene(\"beats\")),\n    am.Event(5500, am.Event.layer_scene(\"fdg_down\")),\n    am.Event(5788, lambda c: c.set_generator_data(\n        \"fdg_down\", 0, \"text\", data_strings[\"fdg_down_0\"]\n    )),\n\n    am.Event(5916, lambda c: c.set_generator_data(\n        \"fdg_down\", 0,\n        \"text\", data_strings[\"fdg_down_1\"], \"lineno\", 0, \"offset\", 0\n    )),\n\n    am.Event(5916, lambda c: c.set_generator_data(\n        \"fdg_down\", 1,\n        \"text\", data_strings[\"fdg_down_2\"]\n    )),\n\n    am.Event(6270, am.Event.swap_scene(\"beats_lr\")),\n\n    am.Event(6508, am.Event.remove_scene(\"fdg_down\")),\n    # am.Event(6508, am.Event.remove_scene(\"debug_counter\")),\n\n    am.Event(6508, am.Event.swap_scene(\"clear\"))\n))\n\n\n# class MixerWrapper:\n#     def __init__(self):\n#         self.is_paused = False\n#\n#     def toggle_music(self):\n#         if self.is_paused:\n#             pygame.mixer.music.unpause()\n#             self.is_paused = False\n#         else:\n#             pygame.mixer.music.pause()\n#             self.is_paused = True\n#\n#\n# ffwing = MixerWrapper()\n\npaused_this_frame = False\nff_this_frame = False\n\nfilename = \"media/credits.wav\"\n\nplayback = Playback()\nplayback.load_file(filename)\n\n# Clear the console before showing the skip menu\nif os.name == \"nt\":\n    os.system(\"cls\")\nelif os.name == \"posix\":\n    os.system(\"clear\")\nelse:\n    print(\"\\033[2J\")\n\nprint(\"\\033[1;1Hskips\\n\\n1 | start\\n2 | title\\n3 | funding\\n4 | loading\\n5 | break\\n6 | final\")\n\n# Skips forward to the title scene\nkey_states = {\n    '1': False,\n    '2': False,\n    '3': False,\n    '4': False,\n    '5': False,\n    '6': False,\n    'p': False,\n    ',': False,\n    '.': False,\n    '/': False\n}\n\ndef on_press(key):\n    try:\n        if key.char in key_states:\n            key_states[key.char] = True\n    except AttributeError:\n        pass\n\ndef on_release(key):\n    try:\n        if key.char in key_states:\n            key_states[key.char] = False\n    except AttributeError:\n        pass\n\nlistener = keyboard.Listener(on_press=on_press, on_release=on_release)\nlistener.start()\n\ntime_menu = time.time()\nwhile time.time() - 2 < time_menu:\n    if key_states['1']:\n        time_menu = 99999999999999999999\n        break\n    elif key_states['2']:\n        skip_beats(controller, 1000, 1081)\n        time_menu = 99999999999999999999\n        break\n    elif key_states['3']:\n        skip_beats(controller, 1770, 1851)\n        controller.events[1849] = am.Event(1849, am.Event.layer_scene(\"redraw_ui\")),\n        controller.events[1860] = am.Event(1860, am.Event.remove_scene(\"redraw_ui\")),\n        time_menu = 99999999999999999999\n        break\n    elif key_states['4']:\n        skip_beats(controller, 3040, 3133)\n        time_menu = 99999999999999999999\n        break\n    elif key_states['5']:\n        skip_beats(controller, 3780, 3898)\n        time_menu = 99999999999999999999\n        break\n    elif key_states['6']:\n        skip_beats(controller, 5420, 5501)\n        time_menu = 99999999999999999999\n        break\n\n    time.sleep(0.01)\n\n\nif os.name == \"nt\":\n    os.system(\"cls\")\nelif os.name == \"posix\":\n    os.system(\"clear\")\nelse:\n    print(\"\\033[2J\")\n\n# wave_obj = sa.WaveObject.from_wave_file(filename)\n# play_obj = wave_obj.play()\nplayback.play()\nplayback.seek(skip_by)\n\ntime_start = time.time()\nlast_update = time.time()\nprev_pos = 0\n\nwhile playback.active:\n    # (17.06.21) might have broken, i used a -1 beat offset here to try and sync up everything better\n    # since i originally used 1-indexed beats\n    #\n    # (24.06.21) update chat it didnt break\n\n    # next_beat = (time.time() - time_start - offset) > ((beat - 1) * delay)\n    next_beat = (playback.curr_pos - offset) > ((beat - 1) * delay)\n    need_update = time.time() - (1/30) > last_update\n    # print(pygame.mixer.music.get_pos())\n\n    if next_beat:\n        controller.request_next()\n        canvas.render_all()\n        last_frames.append(time.time())\n        if len(last_frames) > 10:\n            last_frames.pop(0)\n\n        beat += 1\n\n    if need_update:\n        if key_states['p']:\n            if not paused_this_frame:\n                if playback.paused:\n                    playback.resume()\n                else:\n                    playback.pause()\n    \n                paused_this_frame = True\n        else:\n            paused_this_frame = False\n    \n        if key_states[',']:\n            if not ff_this_frame:\n                ff_this_frame = True\n            else:\n                playback.seek(playback.curr_pos + delay * 3)\n    \n        if key_states['.']:\n            if not ff_this_frame:\n                ff_this_frame = True\n            else:\n                playback.seek(playback.curr_pos + delay * 7)\n    \n        if key_states['/']:\n            if not ff_this_frame:\n                ff_this_frame = True\n            else:\n                playback.seek(playback.curr_pos + delay * 15)\n        # else:\n        #     # print(\"n\", ff_this_frame, pygame.mixer.music.get_pos() + prev_pos, prev_pos)\n        #     if ff_this_frame:\n        #         # print(\"unpausing now\")\n        #         ff_this_frame = False\n        #         ffwing.toggle_music()\n\n        last_update = time.time()\n\n# Add a final clear at the end to prevent the last frame from sticking around when the program ends.\nif os.name == \"nt\":\n    os.system(\"cls\")\nelif os.name == \"posix\":\n    os.system(\"clear\")\nelse:\n    print(\"\\033[2J\")"
  },
  {
    "path": "media/README.txt",
    "content": "I can't really distribute a full .wav file of an entire song that I don't own the rights to.\nThat would be disrespectful.\n\nIf you want to run this on your own device, you'll need to find yourself a copy of\nFrums 「Credits ＬＯＮＧ」\n\nfor yourself.\nSorry.\n\n-----------\nIf you do find a copy, convert it to \"credits.wav\" and drop it in this /media folder."
  },
  {
    "path": "media/credits.txt",
    "content": "Now for the official national weather service forecast for Eastern Massachusets inside of I-495,\nincluding Boston, issued at 7:21 PM, Thursday, October 22nd.\nTonight: Mostly cloudy with isolated showers until midight,\nthen mostly clear after midnight.\nLows in the lower 40s.\nWest winds 10-15 mph with gusts up to 25 mph.\nChance of rain: 20 percent.\n\nFriday: Sunny. Lush colour with highs in the lower 50s.\nNorthwest winds 10-15mph with gusts up to 25mph.\nFriday night, mostly clear.\nLows in the mid-30s.\nNorth winds 10-15mph.\nSaturday: Partly sunny.\nHigh-igh-igh-igh-igh-igh-igh-igh-igh-igh-igh-igh-igh-igh-igh-igh-igh-igh-igh...\n\n\n22.10.2009: Cloudy, precip 20%, temp 43, wind dir 6, 13 (25)\n23.10.2009: Sunny, precip 4%, temp 52, wind dir 7, 12 (25)\n24.10.2009: Partly sunny, precip 7%, temp 48, wind dir 1, 8 (20)\n\n25.10.2009 and beyond: randomly mutate value.\nPrefer 33% precip, move wind dir by +-3 each day, gust = speed x 2.1 - 2.6\n\n\nFunding for this program was made possible by by by by by\nFun-by-by-by-by-ding by-by-by-by-for thiiiii\nProgram. Program. Pro-pro-pro-pro-pro-gram.\nFunding for-by-by-made possible by viewers like you.\nlike you. like you. like you. like you. like you. like you\n...\n\n...\nAnnual financial suppor-\nAnnual financial suppor-\nAnnual financial suppor-\nAnnual financial suppor-\nAnnual financial suppor-\nAnnual financial suppor-\nAnnual financial suppor-\nAnnual financial suppor-\nAnnu- Annu- Th-th-th-thank you.\nBy-ci-po-po-cor-cor-by-by-rrrrrrroooooaaaaaa-\n-wers-ble-b-b-F-f-fi-i-naaaaaaaa-\nFun-funding, fu-fun-fu-fun-funding\nFu-fu-fun-funding, fu-fun-fu-funding\nF-i-n-a-n--po-po-cor-by-por-portio-portio-portion-nnn\nB-b-b-by b-by b-by b-by\n>>>>>\nBy-by-finan-b-b-nc-nc-\nCorrrrrr\nViewers like you. like you. like you.\nFunding-Funding-Fun-fu-funding\nFu-fu-fu-fu-Fu-fu-fu-fu-\nCorporationnnn\nThe corportation for public broadcasting and bi-annual fiii-\nof-nan-for-financial su-for financial asssss-\nin-viewers-you-\nThis is PBS.\n...\n\n...\nHere are the 7 PM observations for the Boston metropolitan area.\nAt Logan Airport, it was cloudy.\nThe temperature as 68 degrees.\nThe dew point, 47 -\nand the relative humidity, 46 percent.\nThe wind was southwest at 13 miles an hour.\nThe pressure was 29.99 inches and rising.\nElsewherrrrrrrrrrrrr\n...\n\n...\nFunding for this program was made possible by viewers..."
  },
  {
    "path": "ocean.py",
    "content": "# adapted from the-sea.js by plaao (me)\n\n# The ocean is stored as a list of strings which is combined into one string when rendered.\n# It is rendered internally in 40x8 resolution.\nimport math\nimport random\n\nocean_time = math.floor(random.random() * 2000)\nalphabet = \"abcdefghijklmnopqrstuvwxyz\"\n\n\ndef init_populate_ocean():\n    global ocean_time\n\n    # Create 12 lists of 80 chars each and get an ocean slice for every ocean time value needed.\n    ocean_base = get_ocean_slices(ocean_time, ocean_time + 80)\n\n    ocean_time += 80\n    return ocean_base\n\n\ndef get_ocean_slice(xr, glitch):\n    x = xr / 5\n    c = math.cos(0.2 * x) + math.sin(0.3 * x) * math.sin(0.23 * x)\n    y = -math.floor(2 * c * math.sin(x)) + 3\n\n    # Returns a list of chars which can then be populated into the ocean.\n    cont_list = []\n    for i in range(10):\n        if random.random() <= 0.002 * glitch:\n            cont_list.append(alphabet[math.floor(random.random() * len(alphabet))])\n\n        else:\n            if i == y:\n                cont_list.append(\"#\")\n            elif i > y:\n                cont_list.append(\".\")\n            else:\n                cont_list.append(\" \")\n\n    return cont_list\n\n\ndef mutate_text(txt, glitch):\n    for i in range(len(txt)):\n        ch = txt[i]\n        if ch in \"#. \" and random.random() <= (0.0002 + ((ocean_time % 1200) / 1200000)) * glitch:\n            txt = txt[:i] + alphabet[math.floor(random.random() * len(alphabet))] + txt[i + 1:]\n\n    return txt\n\n\ndef get_ocean_slices(x1, x2):\n    cont_list = [\n        \"\",\n        \"\",\n        \"\",\n        \"\",\n        \"\",\n        \"\",\n        \"\",\n        \"\",\n        \"\",\n        \"\"\n    ]\n\n    for xr in range(x1, x2):\n        x = xr / 5\n        c = math.cos(0.2 * x) + math.sin(0.3 * x) * math.sin(0.23 * x)\n        y = -math.floor(2 * c * math.sin(x)) + 3\n\n        # Returns a list of chars which can then be populated into the ocean.\n        for i in range(10):\n            if random.random() <= 0.002:\n                cont_list[i] += alphabet[math.floor(random.random() * len(alphabet))]\n\n            else:\n                if i == y:\n                    cont_list[i] += \"#\"\n                elif i > y:\n                    cont_list[i] += \".\"\n                else:\n                    cont_list[i] += \" \"\n\n    return cont_list\n\n\ndef update_ocean_slices(ocean_content, ocean_glitch):\n    global ocean_time\n    \n    # edits in place\n    # get the new slice\n    ocean_slice = get_ocean_slice(ocean_time, ocean_glitch)\n    ocean_time += 1\n    \n    # for every line in content, remove the first char and add the slice char\n    for i in range(10):\n        ocean_content[i] = ocean_content[i][1:] + ocean_slice[i]\n\n    raw_txt = unpack_content_to_text(ocean_content)\n    \n    return mutate_text(raw_txt, ocean_glitch)\n\n\ndef unpack_content_to_text(content):\n    # Join every line with \\n\n    return \"\".join(line for line in content)\n\n\ndef begin_ocean():\n    return init_populate_ocean()\n"
  },
  {
    "path": "requirements.txt",
    "content": "just_playback\npynput; sys_platform == \"darwin\"\nkeyboard; sys_platform != \"darwin\"\n"
  },
  {
    "path": "string_defs.py",
    "content": "# Contains strings used inside credits.py.\n# Kept inside this file to reduce clutter.\n# Implemented as a dictionary of in-memory strings.\nfrom animation_functions import split_word_template, fuck_up_text, work_out_date\n\ndata_strings = {}\n\ndata_strings[\"ocean_b_0\"] = (\n    \"Now for the official national~ weather~ service~ forecast\\n\"\n    \"~~~~for~~ Eastern Massachusetts~ inside of~~ I-~~~~4~~~~9~~~~~~~5~~,\\n\"\n    \"~~~~~~~~    including Boston,\\n\\n\"\n    \"~~~~~~~~issued at 7~~~~:2~~1~~~~ PM~~~~, ~~~~~~~~~~~~~~Thursday, October~~ 2~~2~~nd.\"\n)\n\ndata_strings[\"ocean_b_1\"] = (\n    \"Tonight:\\n\\n~~~~~Mostly cloudy with isolated~ showers~ until~~ mid~~~~night,\\n\"\n    \"~~~~~~~~~then mostly clear~~ after~~ mid~night.\\n\"\n    \"~~~~~~~~~Lows in the lower 4~~~~0~~~~s.\\n\"\n    \"~~~~~~~~~West winds 10~ to~~ 1~~5~~ miles~~ an~~ hour\\n\"\n    \"~~~~~with~ gusts~~ up~ to~ 2~~~~5~~~~ miles~~ an~~ hour~~.\\n\"\n    \"~~~~~~~~~~Chance of rain:~~~~ 2~~0~~~~ per~cent.\"\n)\n\ndata_strings[\"ocean_b_2\"] = (\n    \"Friday:\\n\\n~~Sunny.\\n~~~~~~~~~~~~~~~~Lush colour with highs in the low~er 5~~~0~s.\\n\"\n    \"~~~~~~~~~~Northwest~~ winds~~ 10~~-~1~~5~~ miles~~ an~~ hour\\n\"\n    \"~~~~with gusts up to~~ 2~~~~5~~~~ miles~~ an~~ hour~~.\\n\"\n    \"~~~~~~~~~~~~~~~~Friday night,~~ mostly~ clear.\\n\"\n    \"~~~~~~~~~~~~~~Lows in the mid-3~~~~0~~s.\\n\"\n    \"~~~~~~~~~~~~~~~~North winds 10-1~~~5~~ miles~~ an~~ hour~~.\"\n)\n\ndata_strings[\"ocean_b_3\"] = (\n    \"Saturday:\\n\\n~~~~~~~~Partly sunny.\\n\"\n    \"~~~~~~~~High-@igh-@igh-@igh-@igh-@igh-@igh-@igh-@igh-@igh-@igh-@igh-@igh-@igh-@igh-@igh-@igh-@igh-@igh-i\"\n)\n\ndata_strings[\"ocean_c_0\"] = (\n    \"Here~ are~ the 7~~~~ P~~~~M~~~~ ob~ser~va~tions for~~ the\\n\"\n    \"~~~Bos~ton~~ metro~po~li~tan~~~ ar~ea.\\n\\n\"\n    \"~~~~~~~~~~~~~~~At Logan~~~~ Airport,~~~~~~ it was clou~~~~dy.\\n\"\n    \"~~~~~~~~~~~~~~~The tem~per~a~ture was 6~~~~8~~~~ de~grees,\\n\"\n    \"~~~~~~~~the dew point,~~ 4~~~~7~~ -\\n\"\n    \"~~~~~~and~ the~ re~la~tive~~ hu~mi~di~ty,~~~~ 4~~~~6~~~~ per~~cent.\\n\"\n    \"~~~~~~~~~~~~~~~~The~ wind~ was~ south~~west~ at 1~~~~3~ miles~~~~ an~~~~ ho~ur.\\n\"\n    \"~~~~~~~~~~~~The~ pres~~sure~~ was~~ 2~~~~9~~~~.~~~~~~~~9~~~~~~9~~~~~~ in~ches and ri~sing.\\n\"\n    \"~~~~~~~~Elsewher\" + fuck_up_text(\n        \"rrrrrrrrr\\n\", 800\n    )\n)\n\ndata_strings[\"ocean_c_1\"] = (\n    fuck_up_text(\n        \"Here~ are~ the 7~~~~ P~~~~M~~~~ ob~ser~va~tions for~~ the\\n\"\n        \"~~~Bos~ton~~ metro~po~li~tan~~~ ar~ea.\\n\\n\"\n        \"~~~~~~~~~~~~~~~At Logan~~~~ Airport,~~~~~~ it was clou~~~~dy.\\n\"\n        \"~~~~~~~~~~~~~~~The tem~per~a~ture was 6~~~~8~~~~ de~grees,\\n\"\n        \"~~~~~~~~the dew point,~~ 4~~~~7~~ -\\n\"\n        \"~~~~~~and~ the~ re~la~tive~~ hu~mi~di~ty,~~~~ 4~~~~6~~~~ per~~cent.\\n\"\n        \"~~~~~~~~~~~~~~~~The~ wind~ was~ south~~west~ at 1~~~~3~ miles~~~~ an~~~~ ho~ur.\\n\"\n        \"~~~~~~~~~~~~The~ pres~~sure~~ was~~ 2~~~~9~~~~.~~~~~~~~9~~~~~~9~~~~~~ in~ches and ri~sing.\\n\"\n        \"~~~~~~~~Elsewher\", 100\n    ) + fuck_up_text(\n        \"rrrrrrrrr\\n\", 900\n    )\n)\n\ndata_strings[\"ocean_c_2\"] = (\n    fuck_up_text(\n        \"Here~ are~ the 7~~~~ P~~~~M~~~~ ob~ser~va~tions for~~ the\\n\"\n        \"~~~Bos~ton~~ metro~po~li~tan~~~ ar~ea.\\n\\n\"\n        \"~~~~~~~~~~~~~~~At Logan~~~~ Airport,~~~~~~ it was clou~~~~dy.\\n\"\n        \"~~~~~~~~~~~~~~~The tem~per~a~ture was 6~~~~8~~~~ de~grees,\\n\"\n        \"~~~~~~~~the dew point,~~ 4~~~~7~~ -\\n\"\n        \"~~~~~~and~ the~ re~la~tive~~ hu~mi~di~ty,~~~~ 4~~~~6~~~~ per~~cent.\\n\"\n        \"~~~~~~~~~~~~~~~~The~ wind~ was~ south~~west~ at 1~~~~3~ miles~~~~ an~~~~ ho~ur.\\n\"\n        \"~~~~~~~~~~~~The~ pres~~sure~~ was~~ 2~~~~9~~~~.~~~~~~~~9~~~~~~9~~~~~~ in~ches and ri~sing.\\n\"\n        \"~~~~~~~~Elsewher\", 200\n    ) + fuck_up_text(\n        \"rrrrrrrrr\\n\", 1000\n    )\n)\n\ndata_strings[\"ocean_c_3\"] = (\n    fuck_up_text(\n        \"Here~ are~ the 7~~~~ P~~~~M~~~~ ob~ser~va~tions for~~ the\\n\"\n        \"~~~Bos~ton~~ metro~po~li~tan~~~ ar~ea.\\n\\n\"\n        \"~~~~~~~~~~~~~~~At Logan~~~~ Airport,~~~~~~ it was clou~~~~dy.\\n\"\n        \"~~~~~~~~~~~~~~~The tem~per~a~ture was 6~~~~8~~~~ de~grees,\\n\"\n        \"~~~~~~~~the dew point,~~ 4~~~~7~~ -\\n\"\n        \"~~~~~~and~ the~ re~la~tive~~ hu~mi~di~ty,~~~~ 4~~~~6~~~~ per~~cent.\\n\"\n        \"~~~~~~~~~~~~~~~~The~ wind~ was~ south~~west~ at 1~~~~3~ miles~~~~ an~~~~ ho~ur.\\n\"\n        \"~~~~~~~~~~~~The~ pres~~sure~~ was~~ 2~~~~9~~~~.~~~~~~~~9~~~~~~9~~~~~~ in~ches and ri~sing.\\n\"\n        \"~~~~~~~~Elsewher\", 250\n    ) + fuck_up_text(\n        \"rrrrrrrrr\\n\", 1000\n    )\n)\n\ndata_strings[\"funding_0\"] = (\n    split_word_template(\n        \"Fun####ding#### for#### this#### pro####gram#### was#### made#### pos####si####ble###~\\n\"\n        \"  by###\\n\"\n        \"    by###\\n\"\n        \"      by###\\n\"\n        \"        by###\\n\"\n        \"          by#\\n\"\n        \"Fun#\\n\"\n        \"   by\\n\"\n        \"     by\\n\"\n        \"       by\\n\"\n        \"         by\\n\"\n        \"Funding#\\n\"\n        \"       by\\n\"\n        \"         by\\n\"\n        \"           by\\n\"\n        \"             by\\n\"\n        \"Funding for\\n\"\n        \"Funding for thi#i#i#i#i#\\n\"\n        \"Funding for this pro####gram###\\n\"\n        \"Funding for this pro####gram###\\n\"\n        \"                   pro\\n\"\n        \"                     pro\\n\"\n        \"                       pro\\n\"\n        \"Funding for this pro#gram.#~\\n\"\n        \"Fun####ding#### for#\\n\"\n        \"           by#\\n\"\n        \"             by#\\n\"\n        \"Funding made#### pos####si####ble#### by#### view####ers#### like#### you.###~\\n\"\n        \"####like#### you.##\\n\"\n        \"####like#### you.##\\n\"\n        \"####like#### you.##\\n\"\n        \"####like#### you.##\\n\"\n        \"####like#### you.##\\n\"\n        \"####like#### you.\\n\"\n        \" Fu\\n\"\n        \"  Fu\\n\"\n    ) * 2\n)\n\ndata_strings[\"funding_1\"] = (\n    split_word_template(\n        \"Broad####cast####\\n\"\n        \"Broadcast Cor####por##a####tion.~#####\\n\"\n        \"Cor####po##ra####tion.#####\\n\"\n        \"Cor####po##ra####tion.#####\\n\"\n        \" Cor###po\\n\"\n        \"  Cor###po\\n\"\n        \"    Co\\n\"\n        \"      Co\\n\"\n        \"Cor####po##ra####tion.#####\\n\"\n        \"Cor####po##ra####tion.#####\\n\"\n        \"Cor####po##ra####tion.#####\\n\"\n        \" Cor###po\\n\"\n        \"  Cor###po\\n\"\n        \"    Co\\n\"\n        \"      Co\\n\"\n        \"Cor####po##ra###tion.#\\n\"\n        \" Co\\n\"\n        \"  Co\\n\"\n        \"Cor####po##ra###tion.#\\n\"\n        \" Co\\n\"\n        \"  Co\\n\"\n        \"Cor####po##ra###tion.#\\n\"\n        \" Co\\n\"\n        \"  Co\\n\"\n        \"Cor####po##ra###tion.#\\n\"\n        \" Co\\n\"\n        \"  Co\\n\"\n        \"Cor####po##ra###tion.#\\n\"\n        \" Co\\n\"\n        \"  Co\\n\"\n        \"Cor####po##ra###tion.#\\n\"\n        \" Co\\n\"\n        \"  Co\\n\"\n        \" Cor###po\\n\"\n        \"  Cor###po\\n\"\n        \"    Cor###po\\n\"\n        \"      Cor###po\\n\"\n        \"...#######\\n\"\n        \" Cor###po#\\n\"\n        \"     cor##\\n\"\n        \"  cor###po#\\n\"\n        \"    cor###po#\\n\"\n        \" cor##\\n\"\n        \"      cor###po#\\n\"\n        \"  cor###po#\\n\"\n        \"         cor##\\n\"\n        \"     cor###po#\\n\"\n        \" cor##\\n\"\n        \"   cor##\\n\"\n        \"     cor##\\n\"\n        \"       co#\\n\"\n        \"     co#\\n\"\n        \" cor###po#\\n\"\n        \"   cor##\\n\"\n        \" cor###po#\\n\"\n        \"   cor###po#\\n\"\n        \"     cor##\\n\"\n        \"       cor##\\n\"\n        \"         cor###po#\\n\" + fuck_up_text(\n\n            \" cor###po#\\n\"\n            \"   cor#\\n\"\n            \" cor###po#\\n\"\n            \"   cor#\\n\"\n            \"     cor#\\n\", 100, \"# \"\n        ) + \" ...#\\n\" + fuck_up_text(\n\n            \" co#\\n\"\n            \"  co#\\n\"\n            \" cor###po#\\n\"\n            \"   cor##\\n\"\n            \"     cor###po#\\n\"\n            \"   cor###po#\\n\"\n            \"            cor##\\n\"\n            \"      cor###po#\\n\", 280, \"# \"\n        ) + fuck_up_text(\n\n            \"             cor###po#\\n\"\n            \"    cor##\\n\"\n            \"                  cor###po#\\n\"\n            \"  cor#\\n\"\n            \"          cor#\\n\"\n            \"                    cor#\\n\", 500, \"# \"\n        ) + fuck_up_text(\n\n            \" co#\\n\"\n            \"                co#\\n\"\n            \"     cor###po#\\n\"\n            \"                        cor#\\n\"\n            \"  cor###po#\\n\"\n            \"           cor###po#\\n\"\n            \"             cor#\\n\"\n            \"  cor###po#\\n\", 650, \"# \"\n        ) +\n\n        \" ???###p?#\\n\"\n        \"   ??r#\\n\"\n        \"           ???###??#\\n\"\n        \"                       co?#\\n\"\n        \"                                       ???#\\n\"\n    \n        \"....#....#....#....#....#....#....#....#...#...#...#...#..#..\\n\"\n        \"              <##C##O##N##N##E##C##T##I##O##N## ##L##O##S##T##>\"\n        \"##########################################################################################################\"\n        \"##########################################################################################################\"\n        \"##########################################################################################################\"\n        \"##########################################################################################################\"\n    )\n)\n\ndata_strings[\"funding_2\"] = (\n    split_word_template(\n        \"--####--####-- ####-i####-a-####---- ####-up####-o-#\\n\" +\n        \"-n####nu####-l ####fi####nan####cial ####sup####por#\\n\" +\n        \"An####nu####al ####fi####nan####cial ####sup####por#\\n\" * 5 +\n        \"An####nu###\\nAn####nu###\\n\"\n    )\n)\n\ndata_strings[\"fundingx2_0\"] = (\n    split_word_template(\n        \" By\\n\"\n        \"  ci\\n\"\n        \"    po\\n\"\n        \"      po\\n\"\n        \"  cor#\\n\"\n        \"    cor#\\n\"\n        \"        by#\\n\"\n        \"          by#\\n\"\n        \"rr#rr#rr#ro#oo#oo#aa#aa#aa#\\n##\"\n        \"  wers#\\n\"\n        \" ble#\\n\"\n        \"       b\\n\"\n        \"         b\\n\"\n        \"           F#\\n\"\n        \"         f\\n\"\n        \"       fi\\n\"\n        \"     i\\n\"\n        \"na#aa#aa#aa#aa#aa#aa#aa#\\n\"\n        \" Fun\\n\"\n        \"Fun####ding#\\n\"\n        \" Fu#\\n\"\n        \"  Fun#\\n\"\n        \" fu#\\n\"\n        \"Fun####ding#\\n\"\n        \" Fu#\\n\"\n        \"  Fun#\\n\"\n        \" fu#\\n\"\n        \"Fun####ding#\\n\"\n        \" Fu#\\n\"\n        \"  Fun#\\n\"\n        \" fu#\\n\"\n        \"Fun####ding#\\n\"\n        \"F\\n\"\n        \"    Fi\\n\"\n        \"   Fin\\n\"\n        \"          Fina\\n\"\n        \"      Finan\\n\"\n        \"po\\n\"\n        \"po\\n\"\n        \"cor#\\n\"\n        \"by#\\n\"\n        \"  por\\n\"\n        \"Por#tio##\\n\"\n        \"Por#tio##\\n\"\n        \"Por#tion# nn#nn#nn\\n\"\n        \"b\\n\"\n        \" b\\n\"\n        \"by###\\n\"\n        \"---- by###\\n\"\n        \"-------- by###\\n\"\n        \"------------ by###\\n\"\n        \"---------------- by#######\\n\"\n        \">>#>>###\\n\"\n        \"By#\\n\"\n        \"  by#\\n\"\n        \"fi#nan#\\n\"\n        \" b#\\n\"\n        \"  b#\\n\"\n        \"   nc#\\n\"\n        \"    nc#\\n\"\n        \"Corr#rr#rr#\\n\"\n        \"View####ers#### like#### you.#\\n\"\n        \"      like#### you.#\\n\"\n        \"    like## you.#\\n\"\n        \"Fun####ding###\\n\"\n        \"Fun####ding###\\n\"\n        \"  Fun###\\n\"\n        \"    Fu#\\n\"\n        \"Fun####ding###\\n\"\n        \"  fu#\\n\"\n        \"    fu#\\n\"\n        \"      fu#\\n\"\n        \"        fu#\\n\"\n        \"          fu#\\n\"\n        \"Cor####por####a####tion\\n\"\n        \"The#### cor####por####a####tion#### for#### pub####lic####\"\n        \" broad####cast####ing#### and#### bi####-an####nual#### fii#ii#i\\n\"\n        \"of#\\n\"\n        \"nan#\\n\"\n        \"for#\\n\"\n        \"fin####an####cial#### su#\\n\"\n        \"for#### fin####an####cial#### ass#ss#ss\\n\"\n        \"in#\\n\"\n        \"view####ers###\\n\"\n        \"you#####\\n\"\n        \"| ########This######## is######## P####B####S!############~\\n\"\n    )\n)\n\ndata_strings[\"fundingx2_1\"] = (\n    split_word_template(\"\\n\".join(\n        \"{}#### Unknown###\".format(\n            work_out_date((64 * 3390) + i * 64)).replace(\".\", \".####\") for i in range(26)\n    ) + \"\\n| ########This######## is######## P####B####S!############~\\n\")\n)\n\ndata_strings[\"fundingx2_2\"] = (\n    split_word_template(\n        \"S#e#a#r#c#h#i#n#g# #f#o#r# #a#c#c#e#s##s## ##p##o##i##n##t\"\n        \"########.########.########.########\\n\" +\n        \"Searching for access point########.########.########.########\\n\" * 11 +\n        \"Found:################\"\n        \" P#B#S# #O#f#f#i#c#i#a#l# #1#1#.#### #R#e#s#t#a#r#t#i#n#g#.####.####.####\"\n    )\n)\n\ndata_strings[\"fdg_single_0\"] = (\n    split_word_template(\n        \"########.########.########.########.########.########.########.#######\\n\"\n        \"########.########.########.########.########.########.########.#######\\n\"\n        \"########.########.########.########.########.########.########.#######\\n\"\n        \"########.########.########.########.########.########.########.#######\\n\"\n        \"########.########.########.########.########.########.########.#######\\n\"\n        \"########.########.########.########.########.########.########.#######\\n\"\n        \"Fun####ding#### for#### this#### pro####gram#### was#### made#### pos####sible#\\n\"\n        \" 7 > > > by###\\n\"\n        \" 7 > > > > by###\\n\"\n        \" 8 > > > by###\\n\"\n        \" 8 > > > > by###\\n\"\n        \" 9 > > > by###\\n\"\n        \"Fun##\\n\"\n        \"     by\\n\"\n        \"       by\\n\"\n        \"         by\\n\"\n        \"           by\\n\"\n        \"Funding#\\n\"\n        \"     by\\n\"\n        \"       by\\n\"\n        \"         by\\n\"\n        \"           by\\n\"\n        \"for\\n\"\n        \"thi#i#i#i\\n\"\n        \"Pro####gram.#\\n\"\n        \"Pro####gram.#\\n\"\n        \"Pro\\n\"\n        \"  pro\\n\"\n        \"    pro\\n\"\n        \"      pro\\n\"\n        \"Pro####gram.###\\n\"\n        \"Fun####ding#### for####\\n\"\n        \"            by#\\n\"\n        \"              by#\\n\"\n        \"Funding for made#### pos####sible#### by#### view####ers#### like#### you.###\\n\"\n        \"like#### you.#####\\n\"\n        \"##like#### you.#####\\n\"\n        \"##like#### you.#####\\n\"\n        \"##like#### you.#####\\n\"\n        \"##like#### you.#####\\n\"\n        \"##like#### you.###\\n\"\n        \"Fu\\n\"\n        \"  Fu\\n\"\n        \"Fun####ding#### for#### this#### pro####gram#### was#### made#### pos####sible#### by#\\n\"\n        \"                                         by###\\n\"\n        \"                                       by###\\n\"\n        \"                                     by###\\n\"\n        \"                                   by#\\n\"\n        \"Fun###\\n\"\n        \"     by\\n\"\n        \"       by\\n\"\n        \"         by\\n\"\n        \"           by\\n\"\n        \"Funding#\\n\"\n        \"     by\\n\"\n        \"       by\\n\"\n        \"         by\\n\"\n        \"           by\\n\"\n        \"for\\n\"\n        \"thi#i#i#i\\n\"\n        \"Pro####gram.###\\n\"\n        \"Pro####gram.###\\n\"\n        \"Pro\\n\"\n        \"  pro\\n\"\n        \"    pro\\n\"\n        \"      pro\\n\"\n        \"Pro####gram.#\\n\"\n        \"Fun####ding#### for####\\n\"\n        \"            by#\\n\"\n        \"              by#\\n\"\n        \"Funding for made#### pos####sible#### by#### view####ers#### like#### you.###\\n\"\n        \"like#### you.#####\\n\"\n        \"##like#### you.#####\\n\"\n        \"##like#### you.#####\\n\"\n        \"##like#### you.#####\\n\"\n        \"##like#### you.#####\\n\"\n        \"####< RET 200################################################################\\n\"\n    )\n)\n\ndata_strings[\"fdg_down_0\"] = (\n    split_word_template(\n        \"Fun####ding#### for#### this#### pro####gram#### was#\\n\"\n        \"made#### made#### made#### made#### made#### made#### made#### made#### made###\\n\"\n        \"pos####sible#### by#### view####ers#### like#### you.#######\"\n    )\n)\n\ndata_strings[\"fdg_down_1\"] = (\n    split_word_template(\n        \"---####----#### ---#### ----#### ---####----#### ---#\\n\"\n        \"----#### ----#### ----#### ----#### ----#### ----#### ----#### ----#### ----###\\n\"\n        \"---####-----#### --#### ----####---###### ----######## -##-##-##.#######\"\n    )\n)\n\ndata_strings[\"fdg_down_2\"] = (\n    split_word_template(\n        \"Fun####ding#### for#### this#### pro####gram#### was#\\n\"\n        \"made#### made#### made#### made#### made#### made#### made#### made#### made###\\n\"\n        \"pos####sible#### by#### view####ers###### like######## y##o##u##.#######\"\n    )\n)\n"
  }
]