added other random scripts and functions

This commit is contained in:
KS_HTK 2025-08-21 10:41:54 +02:00
parent ef148dbbb6
commit 3f1207a5cd
Signed by: KS_HTK
GPG key ID: A12528D4094FF70C
8 changed files with 1929 additions and 11 deletions

104
aoc13.py Normal file
View file

@ -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()

1357
assets/input Normal file

File diff suppressed because it is too large Load diff

15
assets/test_input Normal file
View file

@ -0,0 +1,15 @@
#.##..##.
..#.##.#.
##......#
##......#
..#.##.#.
..##..##.
#.#.##.#.
#...##..#
#....#..#
..##..###
#####.##.
#####.##.
..##..###
#....#..#

42
dots2.py Normal file
View file

@ -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)

43
fluepdot-img.py Normal file
View file

@ -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)

View file

@ -4,12 +4,13 @@ When running this file it will randomly select a transition and a frame to trans
""" """
# requires: python-fluepdot, python-dotenv # requires: python-fluepdot, python-dotenv
import os import os
from math import sqrt
from dotenv import load_dotenv from dotenv import load_dotenv
from fluepdot import Fluepdot, Mode from fluepdot import Fluepdot, Mode
from random import choice, sample from random import choice, sample
from itertools import product from itertools import product
from time import sleep from time import sleep
from typing import List from typing import Generator
DELAY = 5 # hold time for completed animations/frames DELAY = 5 # hold time for completed animations/frames
ANIMATION_DELAY = 0.1 # delay between frames of animations 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)] 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) 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") fd.post_text("flipdot e.V.", x=7, y=0, font="fixed_7x14")
original_frame = fd.get_frame() original_frame = fd.get_frame()
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] 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") fd.post_text("Kurhecken", x=14, y=-2, font="fixed_10x20")
frame = [[c == "X" for c in row] for row in fd.get_frame()] frame = [[c == "X" for c in row] for row in fd.get_frame()]
fd.set_mode(Mode.DIFFERENTIAL) fd.set_mode(Mode.DIFFERENTIAL)
@ -89,25 +90,48 @@ def kurhacken(fd: Fluepdot) -> List[List[bool]]:
return frame 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") fd.post_text("37c3Unlocked", x=-1, y=-1, font="DejaVuSerif16")
frame = [[c == "X" for c in row] for row in fd.get_frame()] frame = [[c == "X" for c in row] for row in fd.get_frame()]
return 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") fd.post_text("38C3 Illegal Instructions", font="DejaVuSerif16")
frame = [[c == "X" for c in row] for row in fd.get_frame()] frame = [[c == "X" for c in row] for row in fd.get_frame()]
return 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") fd.post_text("Hackumenta", x=3, y=-1, font="DejaVuSerif16")
frame = [[c == "X" for c in row] for row in fd.get_frame()] frame = [[c == "X" for c in row] for row in fd.get_frame()]
return 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) fd.set_mode(Mode.DIFFERENTIAL)
current = [[c == "X" for c in row] for row in fd.get_frame()] current = [[c == "X" for c in row] for row in fd.get_frame()]
for i in range(0, len(current[0])): 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) 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) fd.set_mode(Mode.DIFFERENTIAL)
max_x, max_y = fd.get_size() max_x, max_y = fd.get_size()
current = [[c == "X" for c in row] for row in fd.get_frame()] 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) 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) fd.set_mode(Mode.DIFFERENTIAL)
current = [[c == "X" for c in row] for row in fd.get_frame()] current = [[c == "X" for c in row] for row in fd.get_frame()]
while True: while True:
@ -157,7 +181,7 @@ def push_to(fd: Fluepdot, frame: List[List[bool]]):
break break
def roll_to(fd: Fluepdot, frame: List[List[bool]]): def roll_to(fd: Fluepdot, frame: list[list[bool]]):
fd.set_mode(Mode.DIFFERENTIAL) fd.set_mode(Mode.DIFFERENTIAL)
current = [[c == "X" for c in row] for row in fd.get_frame()] current = [[c == "X" for c in row] for row in fd.get_frame()]
while frame: while frame:
@ -174,6 +198,7 @@ if __name__ == "__main__":
fd.clear() fd.clear()
animations = [ animations = [
droplet, droplet,
propagate_wave
] ]
transitions = [ transitions = [
wipe_to, wipe_to,

72
tetris/keys.py Normal file
View file

@ -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

260
tetris/tetris.py Normal file
View file

@ -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()