added other random scripts and functions
This commit is contained in:
parent
ef148dbbb6
commit
3f1207a5cd
8 changed files with 1929 additions and 11 deletions
104
aoc13.py
Normal file
104
aoc13.py
Normal 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
1357
assets/input
Normal file
File diff suppressed because it is too large
Load diff
15
assets/test_input
Normal file
15
assets/test_input
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#.##..##.
|
||||||
|
..#.##.#.
|
||||||
|
##......#
|
||||||
|
##......#
|
||||||
|
..#.##.#.
|
||||||
|
..##..##.
|
||||||
|
#.#.##.#.
|
||||||
|
|
||||||
|
#...##..#
|
||||||
|
#....#..#
|
||||||
|
..##..###
|
||||||
|
#####.##.
|
||||||
|
#####.##.
|
||||||
|
..##..###
|
||||||
|
#....#..#
|
42
dots2.py
Normal file
42
dots2.py
Normal 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
43
fluepdot-img.py
Normal 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)
|
47
images.py
47
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
|
# 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
72
tetris/keys.py
Normal 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
260
tetris/tetris.py
Normal 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()
|
Loading…
Add table
Add a link
Reference in a new issue