diff --git a/aoc13.py b/aoc13.py new file mode 100644 index 0000000..88b277a --- /dev/null +++ b/aoc13.py @@ -0,0 +1,104 @@ +""" +Animation of solving an AdventOfCode Challenge +""" +# requires: python-fluepdot, python-dotenv + +import os +from time import perf_counter, sleep +from typing import Callable +from dotenv import load_dotenv +from fluepdot import Fluepdot, Mode + +load_dotenv() + +fd = Fluepdot(os.getenv('DOTS_HOST')) +fd.set_mode(Mode.DIFFERENTIAL) + +DELAY = .001 + +def clear(): + x, y = fd.get_size() + arr = [[False for _ in range(x)] for _ in range(y)] + fd.post_frame(arr) + +def profiler(method): + def profiler_method(*arg, **kw): + t = perf_counter() + ret = method(*arg, **kw) + print(f'{method.__name__} method took : {perf_counter()-t:.4f} sec') + return ret + return profiler_method + +def transpose(m): + return [''.join([m[j][i] for j in range(len(m))]) for i in range(len(m[0]))] + +def get_reflection(mirror: list[str]) -> int | Exception: + fdx, fdy = fd.get_size() + for x in range(1, len(mirror)): + sol = True + draw_arr = [[False for _ in range(fdx)] for _ in range(fdy)] + offset = 0 + for ind, (c1, c2) in enumerate(zip(mirror[x:], mirror[x-1::-1])): + for i, c in enumerate(c1): + if c == '#': + draw_arr[ind+offset][i] = True + for i, c in enumerate(c2): + if c == '#': + draw_arr[ind+offset][i+len(c1)+5] = True + if c1 == c2: + draw_arr[ind+offset][len(c1)+5+len(c2)+5] = True + else: + sol = False + if ind+offset == fdy: + fd.post_frame(draw_arr) + sleep(DELAY) + offset -= fdy + else: + fd.post_frame(draw_arr) + sleep(DELAY) + if sol: + fd.post_text(f'Mirror at {x}') + return x + return Exception('No reflection found') + +def get_reflection2(mirror: list[str]) -> int | Exception: + for x in range(len(mirror)): + errors = 0 + errors += sum(s1!=s2 for c1, c2 in zip(mirror[x:], mirror[x-1::-1]) for s1, s2 in zip(c1, c2)) + if errors == 1: + return x + return Exception('No reflection found') + +def get_reflection_line(func: Callable[[list[str]], int], mirror: list[str]) -> int: + # check for vertical reflection across a horizontal line + ind = func(mirror) + if isinstance(ind, int): + return 100*ind + # check for horizontal reflection across a vertical line + ind = func(transpose(mirror)) + if isinstance(ind, int): + return ind + raise ind + +# Part 1: +def part1(mirrors: list[list[str]]) -> int: + return sum([get_reflection_line(get_reflection, mirror) for mirror in mirrors]) + +# Part 2: +def part2(mirrors: list[list[str]]) -> int: + return sum([get_reflection_line(get_reflection2, mirror) for mirror in mirrors]) + +def get_input(): + with open(os.path.dirname(os.path.realpath(__file__))+'/assets/input', 'r', encoding='utf-8') as f: + content = [s.strip().split('\n') for s in f.read().rstrip().split('\n\n')] + return content + +@profiler +def solve(): + mirrors = get_input() + fd.post_text(f'Part 1: {part1(mirrors)}') + sleep(10) + fd.post_text(f'Part 2: {part2(mirrors)}') + +if __name__ == "__main__": + solve() diff --git a/assets/input b/assets/input new file mode 100644 index 0000000..db4142e --- /dev/null +++ b/assets/input @@ -0,0 +1,1357 @@ +#####.. +.#..### +#..#.#. +#..#... +.#..### +#####.. +##...#. +#.###.. +.##.... +#...### +#...### +.##.... +#.###.. +##...#. +#####.. +.#..### +#..#... + +..#..#..##..# +##.#..##..##. +..#.###.####. +.#.#.#...##.. +.##.#.#..#.## +#####..#..... +#####..#..... +.##.#.#..#.## +.#.#.#..###.. +..#.###.####. +##.#..##..##. +..#..#..##..# +..#..#..##..# + +.##.#..##.### +..##..####.## +##.##.###.### +.........#... +##.#...##.... +...#..#...#.. +####.#.####.. +##..##.....## +#####..#..### + +...#...##...#.. +..#....##....#. +..##.#....#.##. +##.....##.....# +##.##.#..#.##.# +##.##......##.# +#####.####.#### +..#..######.... +..#.########.#. + +....#..#..#.. +####.######.. +....######.#. +...##.##.#.#. +##......#.#.. +..##.##..#..# +......###.#.. +##...##.##.## +##...##.##.## +......###.#.. +..##.##..#..# +##......#.#.. +...##.##.#.#. +.....#####.#. +####.######.. +....#..#..#.. +..##.#...#..# + +###.... +####..# +...###. +...#### +##..... +##..##. +..##### +###.... +....##. +##.#..# +##.#### +##..##. +..#.... +..##..# +..#.... + +.#.#..#.#.#.. +...###.#...## +###..####..## +..##.##..#### +#...#.....#.# +.##..#...#.## +#.##.##.#.... +..#..#..#.... +..#..#..#.... + +.###.#...## +.###.#...## +.##.#####.. +##.#....#.# +#.###..#### +#.###..#### +##......#.# + +#.##..#.###.#...# +.#.#....#....#... +.#.#.##.###...#.# +.#....####.#...## +..##..#.#...#..#. +..##..#.#...#..#. +.#....####.#...## +.#.#.##.###...#.# +.#.#....#....#... +#.##..#.###.#...# +..###.##.#.##..#. +##.#...#...#.#..# +##.#...#...#.#..# +..###.##.#.##..#. +#.##..#..##.#...# + +#..#....#.##### +#..#.#......#.. +...#.#......#.. +#..#....#.##### +.######.....#.. +..#.#.#..##.#.# +#..##.##...#### +..#..##.##....# +.#......#.##..# +.###.##.###...# +####.#.######.. +#####.#..###.## +..##.#...####.. +..##.#...####.. +#####.#..###.## + +.#..#.#.##. +##...#..#.# +#.##.#.#.## +########..# +#....##.##. +#.##.#...## +.####.#.... +.#..#.#.#.. +##..##.#.#. +......#..## +#.##.#.#..# +.#..#...#.# +.#..#...#.# +#.##.#.#..# +......#..## +##..##.#.#. +.#..#.#.#.. + +...#..#...###.##. +...#......###.##. +..#..#####.#....# +##.#..#.#####.#.. +....#.##...###..# +##.#..#.####.###. +..#.#..#....#.### + +#.##.##......## +..##..##....##. +.#..#.##.##.##. +######.######.# +#....##.#..#.## +......##.##.##. +##...##.####.## +#######.#..#.## +#.##.#........# +..##..#......#. +#....########## + +#.#.#....##....#. +#.....########... +..##..##.##.##.## +.##.##.######.##. +..#.#.#.#..#.#.#. +..#.#.###..###.#. +##.#.....##.....# +..###...####...## +#..##..#....#..## +#..##..#....#..## +..###...####...## + +####..###....#### +......#..##.##..# +.##..##..#....##. +#..#.#...#.##.... +......#.#.#.##..# +....#.#######.... +.##..#####....##. +####.#....####### +#..##...#...##### +#..########..#..# +.##.#...#...#.... +####.#.....##.#.. +####..#####..#### +#..##...##..##### +#..##.#.#...#.... +#..#.#####.##.##. +.....#####.###### + +###.... +..##..# +..##### +###.##. +###.##. +....#.. +...#..# + +...#.#. +...#.## +.####.. +###.... +...##.. +...##.. +###.... +.####.. +...#.## +...#.#. +.##.#.. +.#.#.## +.#..... +#.###.. +.##.#.. +.#..... +#.#..## + +#....##.#.#..#### +#.##.#....##..... +#....#...#...#### +#.##.#......##..# +.#..#....#.##.... +.#..#........#..# +#....######.##..# +......##.######## +............##### +......####.###### +#######.#.#...##. +##..####...#.#..# +######.###..##### +.......###.##.##. +#..#.#.#.###.#### +.####.###.####### +##..######.##.##. + +##..#####..## +#....#.#....# +#....#..####. +#.##.#.#....# +#.##.###....# +.#..#.#..##.. +#######.####. +##..###.#..#. +.#.##.#...... +..##..###..## +.####.#.#..#. +########....# +######..#..#. +#######..##.. +......#.#..#. +.####....##.. +#....##..##.. + +..####.#... +.#...####.. +######.#.## +.....#.#.#. +..##..#.### +..#.###.#.. +.###.##.... +...###...## +...###...## +.###.##.... +..#.###.#.. + +##..#.# +......# +##.#.## +..##.## +##.##.# +...#..# +..#.##. +..#..#. +...#..# +##.##.# +..##.## + +..##...#..#.. +##...###.#... +###.##.#.#..# +##.#...##.#.. +....##.###... +###.###.##... +......##.#.#. +......##.#.#. +###.###.##... +....##.#.#... +##.#...##.#.. + +##.##........## +####.#..##..#.# +####.#..##..#.# +##.###......### +.#..#..#..#..#. +.....#......#.. +....#.#.##.#.#. + +.#.#.##..#. +.#..###.#.# +##..###.#.# +.#.#.##..#. +.##....##.. +###.##..##. +###.##..##. +.##....##.. +.#.#.##..#. +##..###.#.# +.#..###.#.# +.#.#.##..#. +#...##.###. + +##..### +#.##.## +..##... +..##... +##..#.. +#.##.## +.#..#.. + +####..#..#.#..# +.##.#####..#... +#.##.####.#.### +.###..#......## +...#######.##.. +#.#.####..#..## +#.#.####..#..## +...#######.##.. +.###..#......## +#.##.####.##### +.##.#####..#... +####..#..#.#..# +#.##.#..#.#.### +#.##.#..#.#.### +####..#..#.#..# +.##.#####..#... +#.##.####.##### + +.##.###..## +....###..## +...#...##.. +.##.#...... +#.#####..## +..######### +###..##..## +.#.##..##.. +###.#..##.. +#.##...##.. +#.###..##.. +..##....... +.##.#...... +##...###### +##.#.##..## +####.#....# +...##...... + +###.##..#####...# +#...###....###... +.###.#.#..###...# +#.....#...##.###. +#.###..#...####.. +###.#.###.##.#... +###.#.###.##.#... +#.###..#...####.. +#.....#...##.###. +.###.#.#..###...# +#...###.....##... +###.##..#####...# +###..#..#.##.###. +....####.#.##.### +....####.#.##.### + +#.###.# +.##...# +......# +.#.#.#. +.#.#.#. +.....## +.##...# +#.###.# +#.#.### +#.####. +#.####. + +#.#..#.#.##.# +..####..####. +.######.####. +#......##..## +..#..#..#..#. +#.#..#.#.##.# +###..####..## +..#.##..####. +##....###..## +.#.##.#.####. +#.#..#.#....# +.#....#..##.. +##.##.####### +###..######## +.#....#.####. +##....##.##.# +.######...... + +..###...#.# +....###.### +##.###.##.. +###.#..###. +..#.#.#.### +##.#######. +..####..... +####.###... +###.####... +##.#.#.##.# +...#.##.### +###.#..#.#. +###.#..#.#. +...#.##.### +##.#.#.#### + +####.#.###... +####.#.###... +..#.#.####... +#...##...#..# +#...#..#####. +.#.###....... +.#.####....#. +.....#..#.#.# +.#.#...#...#. +..##.##....#. +.###.#..#..#. +.###.#..#..#. +..##.##...... +.#.#...#...#. +.....#..#.#.# + +####...#....# +######.#....# +#..#.####..## +#..#..#.####. +.....#.#....# +####..###..## +.....####..## +#..####..##.. +.##.##..#..#. +.##.#...#..#. +####...#####. + +..##... +#....## +#....#. +..##... +#....#. +##..### +#....#. + +.####.#..#### +..##..#...##. +.####...#.##. +#....###.#### +.#..#...#.#.. +#.##.#...#### +.####...#.... +#.##.##..#..# +.####.#..#..# +.......#..... +..##..#...##. + +###....##.#.. +#...#..###... +##.#.###..... +####...##.#.. +##........#.# +.#...#...#.## +##.##..#...#. +####...###### +###.#...####. +..##.#....#.# +####.##.#.#.# +..##.######.# +#.##.######.# +#.##.######.# +..##.######.# + +#######.#.####. +#.#.#.#.######. +#..#.#.#.####.# +#...###.##..##. +#..####...##... +.#..####.####.# +.#..#.#..####.. +#.##.#...#..#.. +##...#...#..#.. +##...#...#..#.. +#.##.#...#..#.. +.#..#.#..####.. +.#..####.####.# + +..#..#..#..#..# +.#.##.#......#. +........#..#... +.........##.... +........####... +..####...#....# +#......#.##.#.. +.#....#..##..#. +#.#..#.##..##.# +##.##.###..###. +.######......## +.##..##..##..## +.######.#..#.## + +.#....#..#....#.. +#.####.###....### +.#.##.#.#......#. +..#..#....####... +.#.##.#...#..#... +#......#.##..##.# +###..####.####.## +.#....#..######.. +##....##.######.# +##########....### +...##......##.... +..####....####... +#.#..#.#...##...# +#.#..#.##..##...# +##....##.######.# +..#..#..##.##.##. +#......########## + +.#.##.#.### +#.####.#... +#.#..#.##.. +#.#..#.#.## +#..###.#### +...##....## +###..###... + +#.##.#..###.##. +.####.#.#..#..# +......#..##..## +#....###.##.#.. +..##...#.#...## +#.##.##....##.. +#.##.##...#.##. +#.##.##...#.##. +#.##.##....###. +..##...#.#...## +#....###.##.#.. + +.######.#.#.# +.######.#...# +###..###..#.. +#..##..#.#..# +########.##.. +..#..#..#.... +#......###..# +##.##.##.#### +########.#### + +##..#...### +..#.#.##### +###.#.##.## +####.###... +...#.###.## +....##..#.. +###..#..#.. +##.##.#.### +##..##..### +##..#.##... +....##.#..# + +...#..# +...##.. +..#..## +..#..## +...##.. +.#.#..# +##...#. +##.##.. +#....#. +.##.#.. +.##.#.. +#....#. +##.##.. +##...#. +.#.#..# + +...#...#......#.. +....#...######... +##.###.##....##.# +##...#..#.##.#..# +...###.#.####.#.# +#####............ +..#.#....#..#.#.. +...###.#.#..#.#.# +###..#.#......#.# +##...#..#....#..# +##.#..#.#....#.#. + +#.#.###.... +#.#.###..#. +...######## +...######## +#.#.###..#. +#.#.###.... +..#.#.#...# +##.######.. +##....#..## +...##..###. +##.#..#...# +..#...#...# +.#.#..##... + +##...##...##.##.# +...#....#...####. +###.#..#.######## +#..........###### +.#.#....#.#..##.. +...##..##...####. +#.##....##..####. +..#.####.#..####. +..##.##.##..#..#. + +##.#...####..#.## +###.#...##..#..## +#.#...#....##...# +#.#######.#.##... +.##....#.#...#.## +.#....#..##....## +#...##.###.#..### +###.##.###.#.#### +.####..#...###### +.####..#...###### +###.##.###.#.#### + +.##...#..##..#... +####.##########.# +#####.########.## +.....###.##.##... +#..#.....##.....# +.....##..##..##.. +#..#.###.##.###.# +########....##### +....##..#..#..##. +.##.###......###. +....#..#....#..#. +####.....##.....# +....##.######.##. + +.#...#..#.####.#. +.....#....#..#... +#.##..#..#....#.. +.#...#.#........# +.....###.#.##.#.# +.#...##.########. +#.#.............. +###...##..#..#..# +###.#.##..#..#..# + +.#...## +#.#.#.# +.##.#.. +#.##..# +.#..##. +.#.###. +#.###.. +.###... +...#### +..##..# +#..#### +.#####. +.##.##. +.##.##. +.#####. + +...#..##..##### +####......##... +.##.##.....##.. +######.####.#.. +..#####.##..... +#......###..### +.#..#.#..###.## +.#........#.... +......#......## +......#...#..## +.#........#.... +.#..#.#..###.## +#......###..### + +###...... +###.#.##. +...#....# +...#....# +###.#.##. +###...... +###.####. +.#.#....# +..##....# + +...###### +.....#... +....#.... +..#...### +.######## +###..#### +....###.# +.#..#.### +###...### +#.###.#.. +###..#.## +#...###.. +.##.###.. +..#.#.### +..#..#... +#..##.### +#..##.### + +.##....##.... +#..##.#..#.## +####.######.# +#..#..####..# +#####.####.## +#..###....### +######....### +.....#...##.. +.##..##..##.. +#..###.##.### +....#......#. +#####.#..#.## +#..#...##...# + +.###.##.... +###.#.#.... +#####.##### +...###..... +.#.....#### +.###...#### +##.###..#.. +.##.####..# +###..#..##. +#..###..##. +#..##.##..# +##..##.#### +###.#...##. +...#....... +...#....... +###.#...##. +##..##.#### + +#.###..## +###..#### +...##.... +##.##.### +...##...# +##.##.##. +#.####.## +#.####.## +#......#. +#......#. +#.####.## + +..############.#. +####.#...####.### +...#..#.####.#..# +..##.##...#..##.# +..##....#.#.#.### +..##....#.#.#.### +..##.##...#..#..# +...#..#.####.#..# +####.#...####.### +..############.#. +####...####.#.#.. +##..###.##...#.## +####.#...##...... + +...#### +#..#... +...#### +..##.## +..##.## +...#### +#.##... +...#### +##.#.## +...#.## +#...... + +#.#..#### +##.#..#.# +.#...#.#. +.#...#.#. +##.#..#.# +#.#..#### +#.#....#. +#.#....#. +#.#..#### +##.#..#.# +.#..##.#. + +#....#...#..... +#....#..####..# +.#..#.##.###.## +.#..#..#.##...# +..##...##.#.#.# +.#.##...#.#...# +..##....#.##... +######.##..#.#. +######.##..#.#. + +####.##.######... +####....####...## +#####..######..#. +####....######... +.##..##..##..##.. +............#.##. +.............##.# +#..#.##.#..#..### +#############..#. +#..#....#..#.###. +#..#....#..#...## +.####..####.##..# +.##.####.##.##..# +....#..#......### +#..#.##.#..#.#.#. + +.#.#..### +#..####.. +####.#... +####.#... +#..####.. +##.#..### +.#.#.#..# +####..### +.##...... +..#..###. +..#..##.. +..#..##.. +..#..###. +.##...... +####..### + +#.##.##.......#.. +#.##.#....###.#.. +##..##.....###### +.####...###...### +.####.#.#...#.### +.......####..#.## +.####.##...##..#. +..##......#..#### +.####.####.##.### +##..###....##..## +.####.##...###### +#....##.#..###... +.#..#..#.###..... + +..##.##..##.. +..####..##.## +...#.#.#.#.#. +##.#......... +..####...#.#. +##.#.#...##.. +##.....####.. +....###...### +..#.######.#. +..#.######.#. +..#.###...### +##.....####.. +##.#.#...##.. + +.###.##.###.#.#.. +####....####.#..# +#.#..##..#.##.### +.....##.......##. +.#.#.##.#.#.###.# +##..#..#..##....# +#.##....##.##.### +#.#.#..#.#.###... +..............##. +###############.# +###############.# +..............##. +#.#.#..#.#.###... +#.##....##.##.### +##..#..#..##....# +.#.#.##.#.#.###.# +.....##.....#.##. + +.#.##.# +.#.##.# +.###.## +.###.#. +#.#..#. +##.##.. +##..... +...#... +#.#...# +.#.#..# +#..#### +#..#... +######. +###.... +##..... +######. +#..#... + +.#...#.#.......#. +.#.#.##..#..#.### +..###.##.....###. +##..#.###.####### +###.##.....##.#.. +..##.#......##.#. +###...#..#.#....# +###...#..#.#....# +..##.#......##.#. +###.##.....##.#.. +##..#.###.###.### +..###.##.....###. +.#.#.##..#..#.### +.#...#.#.......#. +.#...#.#.......#. + +.....####..## +....#.#...... +######..####. +#...#.##....# +.....#....... +#####.###..## +#..##.#.####. + +.##.##...#.#.#. +.##.##...#.#.#. +..#.###..##.... +.#.##.##..###.. +#.##.##...##..# +####.####.....# +####.####.....# +#.##.##...##..# +##.##.##..###.. + +##.##.... +..#...... +...#.#### +###.#.... +##....##. +...###### +##.#.##.# +.....#..# +..#...... + +#..#..########..# +.##.##.#.##.#.##. +.##.#...#..#...#. +######........### +.....####..#.##.. +#..#............# +#..##.##....##.## +......###..###... +.##...##.##.##... + +..#.##. +##..#.# +##..#.# +..#.##. +#####.. +#.#.... +..##### +####.## +.#..##. +..##### +.#.#.#. +.#..#.# +..##.#. +..##.#. +.#....# + +#....##...##### +...........##.. +##.###.####..## +#....###..#..## +######..####... +.#..#.##.##.#.. +##..##.#####.## +##..###.####.## +..##...#...#### + +..##..##..#.##. +...####........ +.########...... +#.##..##.#.#### +.#......#.#.... +##.#..#.##.#..# +...####.....##. +..#....#..#.##. +###.##.###..##. +.#.#..#.#...... +####..######..# +###.##.####.##. +#....#...#..... +.#.####.#..#### +#..####..#..... + +...##..#.#...#. +...##..#.##..#. +.###...#######. +..#..#..###..#. +.###...#...##.. +..#.#..##.#...# +#..##.##...#... +##...#..####### +##...#..####### + +......#.# +####..#.# +####.##.. +##.....## +..#...... +....####. +....#.##. + +#.#.#.# +.###.#. +....#.# +....#.# +.###.#. +#.#.#.# +#.##### +.#..### +#..#.#. +#.##.#. +#.##.#. +#..#.#. +.#..### +#.##.## +#.#.#.# +.###.#. +....#.# + +###.#.#..#.#.#### +#.#.###..###.#.## +####..#..#..##### +##...##...#...### +..#.########.#... +...##.#..#.##.... +.#.#..####..#.#.. +##.#.#....#.#.### +###.#......#.#### + +...#......#.. +......##..... +##..######..# +###..#..##.## +...########.. +###.######.## +####.####.### +...#......#.. +##....##....# +...#..##..#.. +..###....###. + +#...#...#.#...### +...#...#.##..##.. +.###..#...#..#### +#.##.##...##.#### +..#.##..##.##.#.. +###...#..##.##.## +...##.#.##.####.. +...##.#.##.#.##.. +###...#..##.##.## +..#.##..##.##.#.. +#.##.##...##.#### +.###..#...#..#### +...#...#.##..##.. + +....#....#..##### +....#....#..##### +##..####..#.#.### +###...#.....##.## +....######.##..## +##.##...##...###. +.#..#####...#...# +....#.#..####..#. +..##.###.#.#.#... + +#.#.#...#.. +.##..###..# +#.####...#. +#.##.#...#. +.##..###..# +#.#.#..###. +........#.# +.#.#.#..#.. +.#.#.#..#.. +........#.# +#.#.#..###. +.##..###..# +#.##.#...#. +#.####...#. +.##..###..# + +..##.##.##.##.##. +####..##..##..### +..##..######..##. +..#.##.#..#.##.#. +##..###.##..##..# +##..##########..# +##.#....##....#.# +####.#.#..#.#.### +....####..####... +..##..#....#..##. +.......####...... +....#.#....#.#... +####..........### +..###.##..##.###. +###....####....## +####....##....### +###..#.####.#..## + +#....###. +#...##.## +.#...##.. +..#.#.##. +#.#..#.## +#.#..#.## +..#.#.##. +.#...##.. +#...##.## +#....##.. +.##.....# +#.##.###. +#..#.#..# +.#....#.# +.#....#.# + +#..#### +#..#.#. +#...### +##.#### +....#.. +.###.## +.#..#.. +#.##.## +##.#... +..#..## +..#..## +##.#... +#.##.## + +.##.#...# +##.#.##.. +...#..... +########. +####..#.# +###...#.. +.#..####. +#..#..##. +#####..#. +#####..#. +#..#..#.. +#..#..#.. +#####..#. +#####..#. +#..#..##. + +......#...#.#.... +.##.#####..##.### +#..#.##.#.##.###. +.##....#..#.#..## +.##....#..#.#..## +#..#.#..#.##.###. +.##.#####..##.### +......#...#.#.... +....#..#..#..##.# +.##.#...##.#####. +.##..##.....##.## +.##.#####.#..#.#. +....#...#...###.# +#..#...#..####... +.##...##.#..###.# + +#.####.##.#...##. +#.####.##.#...#.. +###....######.#.. +.#..###..#.###### +.##.#..##..###.#. +.##.#..##..###.#. +.#..###..#.###### +###....######.#.. +#.####.##.#...#.. +#.####.##.#...##. +###....#.#..#.... +.###.#..##..##... +.####..#....##.## +#.#####.#####.##. +#..#...#..##.#... + +..####.####.. +.#....#.#..## +.###..#.##... +##.#.##...##. +##.#.##...##. +.###..#.##... +.#....#.#..## +..####.####.. +##.##.#.###.# +##.##.#.##### +..####.####.. +.#....#.#..## +.###..#.##... + +##.##.#.#.# +.#.####..#. +###..##...# +##.####..## +.#..##.#.#. +.#..##.#.#. +##.####.### +###..##...# +.#.####..#. +##.##.#.#.# +#.#.#.###.. +#.#.#.###.. +##.##.#.#.# + +###..##..## +.####..#.## +......#.#.. +##.###.#.## +.#.#.#.#.## +...#...#### +...#..##.## +.#.#.##.... +#.####...## +###.##.##.. +######.##.. +#.####...## +.#.#.##.... + +.#..#..#... +#######.#.. +#.##.#..#.. +........### +#....###... +##..##.#... +#########.. +##..##.##.. +######..... +.......#### +##..###.#.. +......#.### +#...####... +#.##.##.#.. +########.## + +.....#...#..# +..#....#.#### +#.###.#..#### +..#.#....#### +##..#.#.##### +####.#.##.... +..#.####..##. +..##.#..##..# +##.##.####..# +####.#.#.#..# +....#..###..# +###.#.####..# +###.....##..# +##...#.#.#### +..####..##..# + +##...##....##.. +......#..##..## +#..###.#.#..##. +..##.#.##..#.## +..##.#.##..#.## +#..###.#.#..##. +......#..##..## +##...##....##.. +###.#..#....#.. +.....#.###.#.#. +.#..#.#.###.#.. +....#.#####.... +.#...###....##. +...###...#..... +...###...#..... +.#...###....##. +...##.#####.... + +###...#.. +##..##.## +.##....## +.#.#.#### +...##.... +....#..## +...###... +#...###.. +.#.#..### +.....#... +#######.. +######... +.....#... + +..#..#..#.. +#.##.#..#.# +#..##.##.## +#.###.##.## +#.##.#..#.# +..#..#..#.. +..#.#.##.#. +.##.##..##. +##...#..#.. + +.#.#.####.#.#.##. +####......####### +####......####### +.#.#.####.#.#.##. +....######....#.. +.#..........#.##. +.#..##..##..#.#.. +##.##.##.##.##..# +####..##..####... +#.##..##..##.#### +#...#....#.#.###. + +#..##..## +.###.#... +#.#...... +#.#...... +.###.#... +#..##..## +...#.#### +##.##..## +#.##..... +######... +....##.#. +#.###..## +##..#.### + +##.####...... +#####...##..# +.#..#.##...## +.#.#.##.#.#.. +..##.#..#...# +#.########.#. +#.#######..#. +#.#######..#. +#.########.#. diff --git a/assets/test_input b/assets/test_input new file mode 100644 index 0000000..3b6b5cc --- /dev/null +++ b/assets/test_input @@ -0,0 +1,15 @@ +#.##..##. +..#.##.#. +##......# +##......# +..#.##.#. +..##..##. +#.#.##.#. + +#...##..# +#....#..# +..##..### +#####.##. +#####.##. +..##..### +#....#..# diff --git a/dots2.py b/dots2.py new file mode 100644 index 0000000..271139e --- /dev/null +++ b/dots2.py @@ -0,0 +1,42 @@ +# This Project is intended for the fluepdots from flipdot e.V. + +import os +from dotenv import load_dotenv + +from fluepdot import Fluepdot, Mode +from images import propagate_wave + + +def main(fd): + fd.set_mode(Mode.DIFFERENTIAL) + x, y = fd.get_size() + print(f'{x=}, {y=}') + print(fd.get_fonts()) + propagate_wave(fd) + clear(fd) + + +def interleave(func1, func2): + for i in func1: + for j in func2: + for y in range(len(i)): + for x in range(len(i[y])): + i = not i if j[y][x] else i + yield i + + +def invert(arr): + return [[not i for i in j] for j in arr] + + +def clear(fd): + x, y = fd.get_size() + arr = [[False for _ in range(x)] for _ in range(y)] + fd.post_frame(arr) + + +if __name__ == '__main__': + load_dotenv() + fluepdot = Fluepdot(os.getenv('DOTS_HOST')) + clear(fluepdot) + main(fluepdot) diff --git a/fluepdot-img.py b/fluepdot-img.py new file mode 100644 index 0000000..b090106 --- /dev/null +++ b/fluepdot-img.py @@ -0,0 +1,43 @@ +""" +Post fluepdot text to display +""" +# requires: python-fluepdot, python-dotenv + +import os +from dotenv import load_dotenv +from fluepdot import Fluepdot + +load_dotenv() + +fd = Fluepdot(f'http://{os.getenv('DOTS_HOST')}') + +fluepdot = (" XXX XX XX XX \n" + " XXXX XX XX XX \n" + " XX XX XX XX \n" + " XXXXX XX XX XX XXXX XX XXX XXX XX XXXX XXXXX\n" + " XXXXX XX XX XX XXXXXX XXXXXXX XXXXXXX XXXXXXX XXXXX\n" + " XX XX XX XX XX XX XX XX XX XXX XXX XX XX \n" + " XX XX XX XX XXXXXXXX XXX XX XX XX XX XX XX \n" + " XX XX XX XX XXXXXXXX XX XX XX XX XX XX XX \n" + " XX XX XX XX XX XX XX XX XXX XX XX XX \n" + "XX XX XX XXX XXX XX XXX XX XX XX XX XXX XX \n" + "XX XX XXXXXXXX XXXXXXX XXXXXXX XXXXXXX XXXXXXX XXXX \n" + "XX XX XXXX XX XXXXX XX XXX XXX XX XXXX XXX \n" + " XX \n" + " XX \n" + " XX ") + + + +frame = [[x == "X" for x in s] for s in fluepdot.split("\n")] +fd.post_frame(frame, center=True) + +x, y = fd.get_size() + +for i, l in enumerate(frame): + pad = (x - len(l)) // 2 + frame[i] = [False]*pad + l + [False]*pad + +pacman = fd.get_frame() +for l in pacman: + print(l) diff --git a/images.py b/images.py index 4d84d5c..853696d 100644 --- a/images.py +++ b/images.py @@ -4,12 +4,13 @@ When running this file it will randomly select a transition and a frame to trans """ # requires: python-fluepdot, python-dotenv import os +from math import sqrt from dotenv import load_dotenv from fluepdot import Fluepdot, Mode from random import choice, sample from itertools import product from time import sleep -from typing import List +from typing import Generator DELAY = 5 # hold time for completed animations/frames ANIMATION_DELAY = 0.1 # delay between frames of animations @@ -34,7 +35,7 @@ flipdot_logo = [ ] -def checkerboard(len_x: int, len_y: int, invert: bool = False) -> List[List[bool]]: +def checkerboard(len_x: int, len_y: int, invert: bool = False) -> list[list[bool]]: return [[(j + i + invert) % 2 == 0 for i in range(len_x)] for j in range(len_y)] @@ -70,7 +71,7 @@ def droplet(fd: Fluepdot): fd.post_frame(frame) -def fd_logo(fd: Fluepdot) -> List[List[bool]]: +def fd_logo(fd: Fluepdot) -> list[list[bool]]: fd.post_text("flipdot e.V.", x=7, y=0, font="fixed_7x14") original_frame = fd.get_frame() frame = [] @@ -80,7 +81,7 @@ def fd_logo(fd: Fluepdot) -> List[List[bool]]: return [[c == "X" for c in row] for row in frame] -def kurhacken(fd: Fluepdot) -> List[List[bool]]: +def kurhacken(fd: Fluepdot) -> list[list[bool]]: fd.post_text("Kurhecken", x=14, y=-2, font="fixed_10x20") frame = [[c == "X" for c in row] for row in fd.get_frame()] fd.set_mode(Mode.DIFFERENTIAL) @@ -89,25 +90,48 @@ def kurhacken(fd: Fluepdot) -> List[List[bool]]: return frame -def event37c3(fd: Fluepdot) -> List[List[bool]]: +def event37c3(fd: Fluepdot) -> list[list[bool]]: fd.post_text("37c3Unlocked", x=-1, y=-1, font="DejaVuSerif16") frame = [[c == "X" for c in row] for row in fd.get_frame()] return frame -def event38c3(fd: Fluepdot) -> List[List[bool]]: +def event38c3(fd: Fluepdot) -> list[list[bool]]: fd.post_text("38C3 Illegal Instructions", font="DejaVuSerif16") frame = [[c == "X" for c in row] for row in fd.get_frame()] return frame -def hackumenta(fd: Fluepdot) -> List[List[bool]]: +def hackumenta(fd: Fluepdot) -> list[list[bool]]: fd.post_text("Hackumenta", x=3, y=-1, font="DejaVuSerif16") frame = [[c == "X" for c in row] for row in fd.get_frame()] return frame -def wipe_to(fd: Fluepdot, frame: List[List[bool]]): +def wave(len_x: int, len_y: int) -> Generator[list[list[bool]], None, None]: + center_x = len_x // 2 + center_y = len_y // 2 + for r in range(max(len_x, len_y)+5): + arr = [[False for _ in range(len_x)] for _ in range(len_y)] + for y in range(len_y): + for x in range(len_x): + for i in [j for j in range(r, 0, -4)][:5]: + dist = sqrt((x - center_x) ** 2 + (y - center_y) ** 2) + adist = int(dist) + if adist == i or adist == i+1: + arr[y][x] = True + yield arr + + +def propagate_wave(fd: Fluepdot, delay: float = ANIMATION_DELAY) -> None: + x, y = fd.get_size() + wave_func = wave(x, y) + for w in wave_func: + fd.post_frame(w) + sleep(delay) + + +def wipe_to(fd: Fluepdot, frame: list[list[bool]]): fd.set_mode(Mode.DIFFERENTIAL) current = [[c == "X" for c in row] for row in fd.get_frame()] for i in range(0, len(current[0])): @@ -119,7 +143,7 @@ def wipe_to(fd: Fluepdot, frame: List[List[bool]]): fd.post_frame(current) -def dither_to(fd: Fluepdot, frame: List[List[bool]]): +def dither_to(fd: Fluepdot, frame: list[list[bool]]): fd.set_mode(Mode.DIFFERENTIAL) max_x, max_y = fd.get_size() current = [[c == "X" for c in row] for row in fd.get_frame()] @@ -138,7 +162,7 @@ def dither_to(fd: Fluepdot, frame: List[List[bool]]): print(r.text) -def push_to(fd: Fluepdot, frame: List[List[bool]]): +def push_to(fd: Fluepdot, frame: list[list[bool]]): fd.set_mode(Mode.DIFFERENTIAL) current = [[c == "X" for c in row] for row in fd.get_frame()] while True: @@ -157,7 +181,7 @@ def push_to(fd: Fluepdot, frame: List[List[bool]]): break -def roll_to(fd: Fluepdot, frame: List[List[bool]]): +def roll_to(fd: Fluepdot, frame: list[list[bool]]): fd.set_mode(Mode.DIFFERENTIAL) current = [[c == "X" for c in row] for row in fd.get_frame()] while frame: @@ -174,6 +198,7 @@ if __name__ == "__main__": fd.clear() animations = [ droplet, + propagate_wave ] transitions = [ wipe_to, diff --git a/tetris/keys.py b/tetris/keys.py new file mode 100644 index 0000000..ad4e8f2 --- /dev/null +++ b/tetris/keys.py @@ -0,0 +1,72 @@ +global isWindows + +isWindows = False +try: + from win32api import STD_INPUT_HANDLE + from win32console import GetStdHandle, KEY_EVENT, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT + isWindows = True +except ImportError as e: + import sys + import select + import termios + + +class KeyPoller(): + def __enter__(self): + global isWindows + if isWindows: + self.readHandle = GetStdHandle(STD_INPUT_HANDLE) + self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT) + + self.curEventLength = 0 + self.curKeysLength = 0 + + self.capturedChars = [] + else: + self.fd = sys.stdin.fileno() + self.new_term = termios.tcgetattr(self.fd) + self.old_term = termios.tcgetattr(self.fd) + + self.new_term[3] = (self.new_term[3] & ~termios.ICANON & ~termios.ECHO) + termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.new_term) + return self + + + def __exit__(self, type, value, traceback): + if isWindows: + pass + else: + termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term) + + def clear(self): + self.capturedChars = [] + + def poll(self): + if isWindows: + if not len(self.capturedChars) == 0: + return self.capturedChars.pop(0) + + eventsPeek = self.readHandle.PeekConsoleInput(10000) + + if len(eventsPeek) == 0: + return None + + if not len(eventsPeek) == self.curEventLength: + for curEvent in eventsPeek[self.curEventLength:]: + if curEvent.EventType == KEY_EVENT: + if ord(curEvent.Char) == 0 or not curEvent.KeyDown: + pass + else: + curChar = str(curEvent.Char) + self.capturedChars.append(curChar) + self.curEventLength = len(eventsPeek) + + if not len(self.capturedChars) == 0: + return self.capturedChars.pop(0) + else: + return None + else: + dr,dw,de = select.select([sys.stdin], [], [], 0) + if not dr == []: + return sys.stdin.read(1) + return None diff --git a/tetris/tetris.py b/tetris/tetris.py new file mode 100644 index 0000000..23cf0ee --- /dev/null +++ b/tetris/tetris.py @@ -0,0 +1,260 @@ +import random +import time +from fluepdot import Fluepdot, Mode +from dotenv import dotenv_values + +from logging import getLogger, basicConfig + +#from keys import KeyPoller +# replacement for own keypoller using pynput +from pynput import keyboard + + +def get_on_press(tetris): + def on_press(key): + if key == keyboard.Key.esc: + tetris.running = False + return False # stop listener + try: + k = key.char + except: + k = key.name + match k: + case "q": + tetris.rotate(True) + case "e": + tetris.rotate() + case "a": + tetris.shift(-1) + case "d": + tetris.shift(+1) + case "space": + tetris.drop() + case _: + print(f'Key pressed: {k}') + + return on_press + + +config = { + "LOGLEVEL": 'INFO', + "TERMINAL_OUT": "False", + "DISPLAY": "True", + **dotenv_values() +} + +tout = config["TERMINAL_OUT"].lower() not in ['false', '0'] +display_out = config["DISPLAY"].lower() not in ['false', '0'] + +log = getLogger("Fluedot-Tetris") +basicConfig(level=config["LOGLEVEL"].upper()) +log.info(f'Fluepdot-Tetris loading with log level {config["LOGLEVEL"]}') +log.info(f'Terminal output is {"enabled" if tout else "disabled"}') + +NUMS = [[[1, 1, 1], [1], [1, 1, 1], [1, 1, 1], [1, 0, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]], + [[1, 0, 1], [1], [0, 0, 1], [0, 0, 1], [1, 0, 1], [1, 0, 0], [1, 0, 0], [0, 0, 1], [1, 0, 1], [1, 0, 1]], + [[1, 0, 1], [1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [0, 0, 1], [1, 1, 1], [1, 1, 1]], + [[1, 0, 1], [1], [1, 0, 0], [0, 0, 1], [0, 0, 1], [0, 0, 1], [1, 0, 1], [0, 0, 1], [1, 0, 1], [0, 0, 1]], + [[1, 1, 1], [1], [1, 1, 1], [1, 1, 1], [0, 0, 1], [1, 1, 1], [1, 1, 1], [0, 0, 1], [1, 1, 1], [1, 1, 1]]] + +# Game settings +WIDTH = 16 +HEIGHT = 45 + +# Define Tetris shapes +# TODO: Implement rotation like TETЯIS +SHAPES = [ + [[1, 1, 1, 1]], # I Shape + [[1, 1], [1, 1]], # O Shape + [[0, 1, 0], [1, 1, 1]], # T Shape + [[1, 1, 0], [0, 1, 1]], # S Shape + [[0, 1, 1], [1, 1, 0]], # Z Shape + [[1, 0, 0], [1, 1, 1]], # L Shape + [[0, 0, 1], [1, 1, 1]] # J Shape +] + + +def _or(a, b): + return a | b + + +def _and(a, b): + return a & b + + +def _2d_boards_apply(board1, board2, func=_or): + rows1, cols1 = len(board1), len(board1[0]) if board1 else 0 + rows2, cols2 = len(board2), len(board2[0]) if board2 else 0 + rows = max(rows1, rows2) + cols = max(cols1, cols2) + result = [[0] * cols for _ in range(rows)] + for i in range(rows): + for j in range(cols): + value1 = board1[i][j] if i < rows1 and j < cols1 else 0 + value2 = board2[i][j] if i < rows2 and j < cols2 else 0 + result[i][j] = func(value1, value2) + return result + + +class Tetris: + def __init__(self, address): + self.running = False + self.fd = Fluepdot(address) + if display_out: + self.fd_size = self.fd.get_size() + else: + self.fd_size = [100, 16] + log.debug(f"fluepdot size is {self.fd_size}") + if display_out: + self.fd.set_mode(Mode.DIFFERENTIAL) + + self.level = 0 + self.board = [[0] * WIDTH for _ in range(HEIGHT)] + self.current_piece = None + self.current_position = (0, 0) + self.next_piece = random.choice(SHAPES) + self.padding = None + self.score = 0 + + def get_score_board(self): + sb = [[]] * 5 + num = self.score + while num > 0: + d = num % 10 + num //= 10 + for i in range(len(NUMS)): + sb[i] = NUMS[i][d] + [0] + sb[i] + return sb + + def get_padding(self): + if HEIGHT < self.fd_size[0]: + pad = self.fd_size[0] - HEIGHT - 1 + padding = [[0] * WIDTH] * pad + [[1] * WIDTH] + if pad > 4: + padding = _2d_boards_apply(padding, self.offset_piece(1, 1, self.next_piece)) + if pad > 10: + sb = self.get_score_board() + if len(sb[0]) <= 16: + padding = _2d_boards_apply(padding, self.offset_piece(WIDTH - len(sb[0]) - 1, 4, sb)) + self.padding = list(reversed(list(map(list, zip(*padding))))) + else: + self.padding = [[0] * WIDTH] + + def new_piece(self): + self.current_piece = self.next_piece + # NES Tetris randomizer + # (8 options, if equal to prev or reroll -> 7 options, final) + self.next_piece = random.choice(SHAPES + [[]]) + if not self.next_piece or self.next_piece == self.current_piece: + self.next_piece = random.choice(SHAPES) + self.get_padding() + self.current_position = (WIDTH // 2 - len(self.current_piece[0]) // 2, 0) + + def offset_piece(self, x, y, piece=None): + if not piece: + piece = self.current_piece + piece = [[0] * x + l for l in piece] + return [[0] * len(piece[0])] * y + piece + + def draw_board(self): + piece = self.offset_piece(*self.current_position) + board = _2d_boards_apply(piece, self.board) + board = list(reversed(list(map(list, zip(*board))))) + board = [p + f for p, f in zip(self.padding, board)] + if display_out: + self.fd.post_frame([[v == 1 for v in l] for l in board]) + if tout: + for l in board: + print("." + "".join(['X' if x else ' ' for x in l]) + ".") + print('') + + def rotate(self, reversed_=False): + for l in self.current_piece: + log.debug(l) + if reversed_: + direction = "counter clockwise" + self.current_piece = list(reversed(list(map(list, zip(*self.current_piece))))) + else: + direction = "clockwise" + self.current_piece = list(map(list, map(reversed, list(map(list, zip(*self.current_piece)))))) + log.debug(f"Rotate piece {direction}.") + + def shift(self, direction: int = 0) -> bool: + x, y = self.current_position + max_x = WIDTH - len(self.current_piece[0]) + x = max(0, min(max_x, x + direction)) + log.debug(f"Shifting the piece {'left' if direction == -1 else 'right'}. {direction}") + if not self.collide((x, y)): + self.current_position = (x, y) + return True + return False + + def drop(self): + log.debug(f"Dropping the piece.") + x, y = self.current_position + oy = y + y_offset = HEIGHT - y + 1 + while not (self.collide(pos=(x, y + y_offset)) and not self.collide(pos=(x, y + y_offset - 1))): + log.debug(f"{y_offset=}, {y + y_offset}") + if self.collide(pos=(x, y + y_offset)): + y_offset //= 2 + else: + y += y_offset // 2 + log.debug(f"Found the drop offset.") + self.current_position = (x, y + y_offset) + self.score += y + y_offset - oy # TODO: softdrop 1 point for each cell, harddrop 2 + self.step() + + def step(self): + log.debug(f"Runing a step.") + if self.collide(): + if self.current_position[1] == 0: + return False + self.board = _2d_boards_apply(self.board, self.offset_piece(*self.current_position)) + self.clears() + self.new_piece() + else: + self.current_position = (self.current_position[0], self.current_position[1] + 1) + return True + + def collide(self, pos=None): + if not pos: + x, y = self.current_position + else: + x, y = pos + y += 1 + piece = self.offset_piece(x, y) + if y + len(self.current_piece) > HEIGHT: + return True + board = _2d_boards_apply(piece, self.board, _and) + return any(map(any, board)) + + def clears(self): + rows = sum(all(l) for l in self.board) + if rows > 0: + self.score += [40, 100, 300, 1200][rows - 1] * (self.level + 1) + self.board = [[0] * WIDTH] * rows + [l for l in self.board if not all(l)] + + def end_game(self): + log.info(f"Game ended. Score: {self.score}") + if display_out: + self.fd.set_mode(Mode.FULL) + self.fd.post_text(f"Score: {self.score}") + + def run(self): + self.new_piece() + #log.debug(self.current_piece) + self.running = True + listener = keyboard.Listener(on_press=get_on_press(self)) + listener.start() + while self.running: + self.running = self.step() + self.draw_board() + time.sleep(.5) # Game loop delay + self.end_game() + + +if __name__ == "__main__": + log.info(f'Connecting to fluepdot at {config["DOTS_HOST"]}') + game = Tetris(config["DOTS_HOST"]) + game.run()