test webhook

This commit is contained in:
2025-09-22 19:18:01 +02:00
parent 940a4e5ad2
commit ad80b1a814
23 changed files with 1007 additions and 0 deletions

3
INSTALL_README.txt Normal file
View File

@@ -0,0 +1,3 @@
python.exe -m pip install --upgrade pip
pip install pillow pyzbar opencv-python numpy pygame pygetwindow requests pyautogui

BIN
fail.ogg Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

656
loader_game.py Normal file
View File

@@ -0,0 +1,656 @@
import pygame
import random
import sys
import os
import subprocess
import numpy as np
import time
SCREEN_WIDTH = 1920
SCREEN_HEIGHT = 1080
FPS = 120
TOP_BAR_HEIGHT = SCREEN_HEIGHT // 8
GAME_AREA_HEIGHT = SCREEN_HEIGHT // 1.6
BOTTOM_BAR_HEIGHT = SCREEN_HEIGHT // 4
TRAILER_WIDTH = int(SCREEN_WIDTH * 0.885416666667)
TRAILER_HEIGHT = int(SCREEN_HEIGHT * 0.2778)
TRAILER_X = (SCREEN_WIDTH - TRAILER_WIDTH) // 4
TRAILER_Y = TOP_BAR_HEIGHT + 200
CASE_SPEED_SLOW = 2
CASE_SPEED_FAST = 6
CASE_COLORS = [(200, 0, 0), (0, 200, 0), (0, 0, 200)]
SNAP_THRESHOLD = 60
FAST_TO_SLOW_DISTANCE = 120
UNIT_LENGTH = TRAILER_HEIGHT // 3
UNIT_HEIGHT = TRAILER_HEIGHT // 3
pygame.init()
pygame.joystick.init()
if pygame.joystick.get_count() > 0:
joystick = pygame.joystick.Joystick(0)
joystick.init()
print(f"Controller erkannt: {joystick.get_name()}")
else:
print("Kein Controller gefunden.")
pygame.mixer.init()
sound_fail = pygame.mixer.Sound("fail.ogg")
sound_fail.set_volume(0.6)
sound_place = pygame.mixer.Sound("place.ogg")
sound_place.set_volume(0.6)
sound_roll = pygame.mixer.Sound("roll.ogg")
sound_roll.set_volume(0.3)
current_case_visible = True
# Animationssteuerung
transition_fps = 60
transition_counter = 0
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
def generate_beep(frequency=440, duration_ms=500, volume=0.5):
sample_rate = 44100
n_samples = int(sample_rate * (duration_ms / 1000.0))
t = np.linspace(0, duration_ms / 1000.0, n_samples, False)
wave = np.sin(2 * np.pi * frequency * t) * volume
audio = (wave * 32767).astype(np.int16)
return pygame.sndarray.make_sound(np.column_stack([audio, audio]))
sound_tut1 = generate_beep(440, 600)
sound_tut2 = generate_beep(440, 400)
sound_tuuut = generate_beep(600, 1000)
pygame.display.set_caption("Habegger Loader Game")
pygame.event.set_grab(True)
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 48)
PLAYING = 1
state = PLAYING
shake_timer = 0
stacked_cases = []
case_sequence = []
case_index = 0
current_case = None
collision_x = TRAILER_X
def load_instruction_image():
try:
image = pygame.image.load("images/tutorial.png")
return pygame.transform.scale(image, (SCREEN_WIDTH, SCREEN_HEIGHT))
except:
return None
instruction_image = load_instruction_image()
def show_instruction_screen():
if not instruction_image:
return
waiting = True
while waiting:
screen.blit(instruction_image, (0, 0))
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE) or \
(event.type == pygame.JOYBUTTONDOWN and event.button == 0):
waiting = False
def generate_fill_sequence():
global case_sequence
try:
with open("case_sequences.txt", "r") as f:
sets = f.read().strip().split("\n\n")
chosen = random.choice(sets).strip().split("\n")
sequence = []
for line in chosen:
parts = line.strip().split(",")
if len(parts) == 3:
w, h, rot = int(parts[0]), int(parts[1]), parts[2].strip().lower() == 'true'
sequence.append({'w': w, 'h': h, 'rotated': rot})
case_sequence = sequence
except:
case_sequence = [{'w': 2, 'h': 1, 'rotated': False}, {'w': 1, 'h': 2, 'rotated': False}]
def load_background():
try:
image = pygame.image.load("images/background.png")
return pygame.transform.scale(image, (SCREEN_WIDTH, GAME_AREA_HEIGHT))
except:
return None
background_image = load_background()
case_images = {}
def load_case_images():
for w in range(1, 5):
for h in range(1, 4):
key = f"case_w{w}_h{h}"
path = f"images/{key}.png"
if os.path.exists(path):
case_images[key] = pygame.image.load(path).convert_alpha()
load_case_images()
class Case:
def animate_snap(self):
pygame.mixer.Channel(1).stop()
global collision_x
# Snap-Animation: langsam einrasten an die Rückwand
target_x = collision_x
steps = 10
delta = (self.x - target_x) / steps
for _ in range(steps):
self.x -= delta
draw_game()
pygame.display.flip()
pygame.time.delay(int(1000 / transition_fps))
self.x = target_x
self.placed = True
self.snapped = True
collision_x = self.x + self.width
def mark_occupied(self):
for base in reversed(stacked_cases):
height_ok = base.y - self.height >= TRAILER_Y
width_ok = self.width <= base.width
space_free = all(not (
other.y + other.height == base.y and
other.x < base.x + base.width and
other.x + other.width > base.x
) for other in stacked_cases) and not getattr(base, 'occupied', False)
if height_ok and width_ok and space_free:
base.occupied = True
break
def can_place_on_top(self):
if self.placed:
return False
for base in reversed(stacked_cases):
if base.y <= TRAILER_Y:
continue
height_ok = base.y - self.height >= TRAILER_Y
width_ok = self.width <= base.width
aligned_with_barrier = abs(base.x + base.width - collision_x) < 5
space_free = all(not (
other.y + other.height == base.y and
other.x < base.x + base.width and
other.x + other.width > base.x
) for other in stacked_cases)
if height_ok and width_ok and aligned_with_barrier and space_free:
return True
return False
def __init__(self, size=None):
self.rotated = False
global case_index
if not case_sequence:
generate_fill_sequence()
if size is None:
size = case_sequence[case_index % len(case_sequence)]
case_index += 1
self.length_units, self.height_units = size['w'], size['h']
if size['rotated']:
self.length_units, self.height_units = self.height_units, self.length_units
elif self.height_units * UNIT_LENGTH <= TRAILER_WIDTH and self.length_units * UNIT_HEIGHT <= TRAILER_HEIGHT and random.choice(
[True, False]):
self.length_units, self.height_units = self.height_units, self.length_units
self.width = self.length_units * UNIT_LENGTH
self.height = self.height_units * UNIT_HEIGHT
self.x = SCREEN_WIDTH
self.spawn_y = TRAILER_Y + TRAILER_HEIGHT - self.height
self.y = self.spawn_y
self.entry_angle = 0
self.color = random.choice(CASE_COLORS)
self.placed = False
self.allow_snap = False
self.snapped = False
self.occupied = False
def move(self):
if not self.placed and not pygame.mixer.Channel(1).get_busy():
pygame.mixer.Channel(1).play(sound_roll, loops=-1)
return
distance_to_obstacle = self.x - collision_x
speed = CASE_SPEED_FAST if distance_to_obstacle > FAST_TO_SLOW_DISTANCE else CASE_SPEED_SLOW
self.x -= speed
if self.x <= collision_x + SNAP_THRESHOLD:
self.allow_snap = True
def collides_with_barrier(self):
return not self.snapped and self.x <= collision_x
def draw(self, surface):
angle = getattr(self, 'entry_angle', 0)
if self.rotated:
key = f"case_w{self.height_units}_h{self.length_units}"
else:
key = f"case_w{self.length_units}_h{self.height_units}"
image = case_images.get(key)
if image:
if self.rotated:
image_to_draw = pygame.transform.scale(image, (self.height, self.width))
else:
image_to_draw = pygame.transform.scale(image, (self.width, self.height))
if self.rotated:
image_to_draw = pygame.transform.rotate(image_to_draw, 90)
if angle != 0:
image_to_draw = pygame.transform.rotate(image_to_draw, angle)
rect = image_to_draw.get_rect(center=(self.x + self.width // 2, self.y + self.height // 3))
surface.blit(image_to_draw, rect)
else:
surface.blit(image_to_draw, (self.x, self.y))
else:
pygame.draw.rect(surface, self.color, (self.x, self.y, self.width, self.height))
def animate_tip(self):
global current_case_visible
current_case_visible = False
key = f"case_w{self.length_units}_h{self.height_units}"
image = case_images.get(key)
if image:
original = pygame.transform.scale(image, (self.width, self.height)).convert_alpha()
else:
original = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
pygame.draw.rect(original, self.color, (0, 0, self.width, self.height))
steps = 10
for step in range(steps + 1):
angle = step * (90 / steps)
rotated = pygame.transform.rotate(original, angle)
new_rect = rotated.get_rect(bottomleft=(self.x, self.y + self.height))
draw_game()
screen.blit(rotated, new_rect)
pygame.display.flip()
pygame.time.delay(int(1000 / transition_fps))
self.width, self.height = self.height, self.width
self.length_units, self.height_units = self.height_units, self.length_units
self.rotated = not getattr(self, 'rotated', False)
self.x = max(collision_x + SNAP_THRESHOLD // 2, self.x)
self.y = TRAILER_Y + TRAILER_HEIGHT - self.height
current_case_visible = True
def animate_place_on_top(self):
pygame.mixer.Channel(1).stop()
global current_case_visible, collision_x
for base in reversed(stacked_cases):
height_ok = base.y - self.height >= TRAILER_Y
width_ok = self.width <= base.width
space_free = all(not (
other.y + other.height == base.y and
other.x < base.x + base.width and
other.x + other.width > base.x
) for other in stacked_cases)
if height_ok and width_ok and space_free:
start_y = self.y
target_y = base.y - self.height
steps = 10
for step in range(steps):
self.y = start_y - ((start_y - target_y) * (step + 1) / steps)
draw_game()
pygame.display.flip()
pygame.time.delay(int(1000 / transition_fps))
self.y = target_y
start_x = self.x
target_x = base.x + base.width - self.width
for step in range(steps):
self.x = start_x - ((start_x - target_x) * (step + 1) / steps)
draw_game()
pygame.display.flip()
pygame.time.delay(int(1000 / transition_fps))
self.x = target_x
self.placed = True
self.snapped = True
self.mark_occupied()
collision_x = self.x + self.width
return True
current_case_visible = True
return False
def snap_to_wall(self):
global collision_x
self.x = collision_x
self.placed = True
self.snapped = True
collision_x = self.x + self.width
def fail_snap(self):
pygame.mixer.Channel(1).stop()
global collision_x
self.x = collision_x + SNAP_THRESHOLD
self.placed = True
collision_x = self.x + self.width
def draw_progress_bar(surface, percent):
bar_width = SCREEN_WIDTH * 0.25
bar_height = 30
x = SCREEN_WIDTH - bar_width - 140
y = TOP_BAR_HEIGHT // 2 - bar_height // 2
gap = 8
segment_width = (bar_width - 4 * gap) / 5
red = min(255, int(percent * 2.55))
green = max(0, 255 - int(percent * 2.55))
color = (red, green, 0)
for i in range(5):
segment_x = x + i * (segment_width + gap)
pygame.draw.rect(surface, (50, 50, 50), (segment_x, y, segment_width, bar_height), border_radius=10)
if percent >= (i + 1) * 20:
pygame.draw.rect(surface, color, (segment_x, y, segment_width, bar_height), border_radius=10)
text = font.render(f"{int(percent)}%", True, (255, 255, 255))
surface.blit(text, (x + bar_width + 10, y))
def draw_title(surface):
title = font.render("Habegger Trailer Load", True, (255, 255, 255))
surface.blit(title, (40, TOP_BAR_HEIGHT // 2 - title.get_height() // 2))
def calculate_load_percent():
total_volume = TRAILER_WIDTH * TRAILER_HEIGHT
used_volume = sum([c.width * c.height for c in stacked_cases])
return min(100, (used_volume / total_volume) * 100)
next_queue = []
def draw_game(hide_trailer=False):
global current_case
global shake_timer
global transition_counter
if transition_counter > 0:
transition_counter -= 1
pygame.time.delay(int(1000 / FPS))
global current_case
global shake_timer
offset_x = random.randint(-5, 5) if shake_timer > 0 else 0
offset_y = random.randint(-5, 5) if shake_timer > 0 else 0
if shake_timer > 0:
shake_timer -= 1
screen.fill((0, 0, 0))
pygame.draw.rect(screen, (0, 0, 0), (0, 0, SCREEN_WIDTH, TOP_BAR_HEIGHT))
draw_title(screen)
percent = calculate_load_percent()
draw_progress_bar(screen, percent)
if background_image:
screen.blit(background_image, (0, TOP_BAR_HEIGHT))
else:
pygame.draw.rect(screen, (0, 0, 0), (0, TOP_BAR_HEIGHT, SCREEN_WIDTH, GAME_AREA_HEIGHT))
if not hide_trailer:
pygame.draw.rect(screen, (100, 100, 100),
(TRAILER_X + offset_x, TRAILER_Y + offset_y, TRAILER_WIDTH, TRAILER_HEIGHT))
for c in stacked_cases:
c.draw(screen)
if current_case and current_case_visible:
current_case.draw(screen)
pygame.draw.line(screen, (255, 0, 0), (collision_x, TRAILER_Y), (collision_x, TRAILER_Y + TRAILER_HEIGHT), 3)
pygame.draw.rect(screen, (0, 0, 0), (0, TOP_BAR_HEIGHT + GAME_AREA_HEIGHT, SCREEN_WIDTH, BOTTOM_BAR_HEIGHT))
if current_case and current_case.allow_snap:
on_top_possible = current_case.can_place_on_top()
if on_top_possible:
snap_text = font.render("OnTop möglich!", True, (255, 255, 0))
else:
snap_text = font.render("Snap bereit!", True, (0, 255, 0))
screen.blit(snap_text, (SCREEN_WIDTH - SCREEN_WIDTH // 3 - snap_text.get_width() // 2,
SCREEN_HEIGHT - BOTTOM_BAR_HEIGHT // 2 - snap_text.get_height() // 2))
# Vorschau "Next"
label = font.render("Next:", True, (255, 255, 255))
screen.blit(label, (40, SCREEN_HEIGHT - BOTTOM_BAR_HEIGHT + 10))
preview_x = 40
for i, case in enumerate(next_queue[:3]):
w_units = case['w']
h_units = case['h']
if case['rotated']:
w_units, h_units = h_units, w_units
key = f"case_w{w_units}_h{h_units}"
image = case_images.get(key)
w = w_units * UNIT_LENGTH
h = h_units * UNIT_HEIGHT
if image:
scaled = pygame.transform.scale(image, (w, h))
screen.blit(scaled, (preview_x, SCREEN_HEIGHT - BOTTOM_BAR_HEIGHT + 40))
else:
pygame.draw.rect(screen, CASE_COLORS[i % len(CASE_COLORS)],
(preview_x, SCREEN_HEIGHT - BOTTOM_BAR_HEIGHT + 40, w, h))
preview_x += max(w, 80) + 20
def spawn_case():
global running
global can_use_on_top
can_use_on_top = True
global case_sequence, case_index, next_queue
if not case_sequence:
generate_fill_sequence()
# Vorschau initial füllen
while len(next_queue) < 3:
index = (case_index + len(next_queue)) % len(case_sequence)
next_queue.append(case_sequence[index])
# aktuelles Case bestimmen
if case_index >= len(case_sequence):
return None
case_data = case_sequence[case_index % len(case_sequence)]
case_index += 1
# Vorschau nachrücken
if next_queue:
next_queue.pop(0)
while len(next_queue) < 3:
index = (case_index + len(next_queue)) % len(case_sequence)
next_queue.append(case_sequence[index])
return Case(size=case_data)
def show_start_sequence():
center_x = TRAILER_X + TRAILER_WIDTH // 2
center_y = TRAILER_Y + TRAILER_HEIGHT // 2
colors = [(255, 0, 0), (255, 200, 0), (0, 255, 0)]
delays = [1000, 1000, 1000] # ms pro Punkt
sounds = [sound_tut1, sound_tut2, sound_tuuut]
radius = 90
for i in range(3):
draw_game(hide_trailer=True)
pygame.draw.circle(screen, colors[i], (center_x, center_y), radius)
sounds[i].play()
pygame.display.flip()
pygame.time.delay(delays[i])
# Reißverschluss-Effekt
zip_sound = pygame.mixer.Sound("zip.ogg")
zip_sound.set_volume(1)
zip_sound.play()
duration = 200
steps = 60
for step in range(steps + 1):
width = int(TRAILER_WIDTH * (step / steps))
draw_game(hide_trailer=True)
pygame.draw.rect(screen, (100, 100, 100), (TRAILER_X, TRAILER_Y, width, TRAILER_HEIGHT))
pygame.display.flip()
pygame.time.delay(duration // steps)
pygame.time.delay(1000)
show_instruction_screen()
show_start_sequence()
current_case = spawn_case()
def show_game_over(score):
try:
with open("last_user.txt", "r", encoding="utf-8") as f:
lines = f.readlines()
username = lines[1].strip() if len(lines) > 1 else "Spieler"
try:
result = subprocess.run(
["curl", "-X", "POST", "https://hag.putschgi.ch/Yb54CO5Ypybx/api/submit_points.php",
"-d", f"name={username}",
"-d", f"score={int(score)}",
"-d", "api_key=hag-trailer-8051"],
capture_output=True, text=True
)
if result.returncode != 0 or not "success" in result.stdout.lower():
raise Exception("Serverfehler")
except:
error_text = font.render("Fehler: Punkte konnten nicht gesendet werden!", True, (255, 100, 100))
screen.blit(error_text, (SCREEN_WIDTH // 2 - error_text.get_width() // 2, SCREEN_HEIGHT // 2 + 60))
pygame.display.flip()
pygame.time.delay(2000)
except:
username = "Spieler"
pygame.mixer.stop()
if score >= 75:
message = f"Gratuliere {username}! Du hast {int(score)}% geladen! Drücke A oder SPACE um zu beenden."
else:
message = f"{username}, du hast {int(score)}% geladen. Drücke A oder SPACE um zu beenden."
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
overlay.set_alpha(220)
overlay.fill((0, 0, 0))
screen.blit(overlay, (0, 0))
text = font.render(message, True, (255, 255, 255))
screen.blit(text, (SCREEN_WIDTH // 2 - text.get_width() // 2,
SCREEN_HEIGHT // 2 - text.get_height() // 2))
pygame.display.flip()
waiting = True
while waiting:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif (
(event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE) or
(event.type == pygame.JOYBUTTONDOWN and event.button == 0)
):
pygame.quit()
sys.exit()
running = True
can_use_tilt = True
can_use_on_top = True
game_finished = False
while running:
if not game_finished and collision_x > TRAILER_X + TRAILER_WIDTH:
show_game_over(calculate_load_percent())
game_finished = True
continue
if not game_finished and current_case is None and case_index >= len(case_sequence):
show_game_over(calculate_load_percent())
game_finished = True
continue
clock.tick(FPS)
if transition_counter > 0:
transition_counter -= 1
draw_game()
pygame.display.flip()
pygame.time.delay(int(1000 / FPS))
continue
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN or event.type == pygame.JOYBUTTONDOWN or event.type == pygame.JOYHATMOTION:
if state == PLAYING:
can_tip = current_case.length_units * UNIT_HEIGHT <= TRAILER_HEIGHT
if (
(event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT) or
(event.type == pygame.JOYHATMOTION and event.value[0] == -1) or
(event.type == pygame.JOYBUTTONDOWN and event.button == 2)
):
if current_case and current_case.allow_snap and can_use_tilt and can_tip:
current_case.animate_tip()
can_use_tilt = False
elif (
(event.type == pygame.KEYDOWN and event.key == pygame.K_UP) or
(event.type == pygame.JOYHATMOTION and event.value[1] == 1) or
(event.type == pygame.JOYBUTTONDOWN and event.button == 3)
):
if current_case and current_case.allow_snap and can_use_on_top and current_case.can_place_on_top():
if current_case.animate_place_on_top():
transition_counter = transition_fps
sound_place.play()
stacked_cases.append(current_case)
current_case = spawn_case()
if current_case is None and not game_finished:
show_game_over(calculate_load_percent())
game_finished = True
continue
can_use_tilt = True
can_use_on_top = True
else:
current_case.fail_snap()
sound_place.play()
stacked_cases.append(current_case)
current_case = spawn_case()
if current_case is None and not game_finished:
show_game_over(calculate_load_percent())
game_finished = True
continue
shake_timer = 10
elif (
(event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE) or
(event.type == pygame.JOYBUTTONDOWN and event.button == 0)
):
if current_case and current_case.allow_snap:
current_case.animate_snap()
can_use_tilt = True
can_use_on_top = True
sound_place.play()
transition_counter = transition_fps
stacked_cases.append(current_case)
current_case = spawn_case()
if state == PLAYING:
if current_case is None:
continue
if current_case:
current_case.move()
if not game_finished:
can_tip = current_case.length_units * UNIT_HEIGHT <= TRAILER_HEIGHT
if collision_x + current_case.width > TRAILER_X + TRAILER_WIDTH and not current_case.can_place_on_top() and not current_case.allow_snap and not can_tip:
show_game_over(calculate_load_percent())
game_finished = True
continue
if calculate_load_percent() >= 99.9 and case_index >= len(case_sequence):
show_game_over(100)
game_finished = True
continue
if not current_case.placed and current_case.collides_with_barrier():
current_case.fail_snap()
if current_case.x + current_case.width > TRAILER_X + TRAILER_WIDTH:
if stacked_cases and stacked_cases[-1] == current_case:
stacked_cases.pop()
else:
stacked_cases.append(current_case)
can_use_tilt = True
can_use_on_top = True
sound_fail.play()
current_case = spawn_case()
shake_timer = 10
draw_game()
pygame.display.flip()
pygame.quit()
sys.exit()

BIN
place.ogg Normal file

Binary file not shown.

188
qr_scan.py Normal file
View File

@@ -0,0 +1,188 @@
import cv2
from pyzbar.pyzbar import decode
import numpy as np
from PIL import ImageFont, ImageDraw, Image
import os
import subprocess
import requests
import pygetwindow as gw
import pyautogui
import time
# Nach 2 Sekunden suchen wir das Fenster
time.sleep(2)
for w in gw.getWindowsWithTitle("QR Scanner"): # oder z. B. "pygame", "Loader", etc.
w.activate()
w.maximize() # optional
pyautogui.click(w.left + 100, w.top + 100) # sicherer Klick ins Fenster
break
API_URL = "https://hag.putschgi.ch/Yb54CO5Ypybx/api/qr.php?action=verify_hash"
API_SECRET = "ddlKC6sgE4EMvsU9VQeIVeYrsPGpE104"
# Fenstergröße & Farben
window_width, window_height = 1920, 1080
# Habegger Gold
COLOR_GREEN = (0, 255, 0)
COLOR_YELLOW = (0, 255, 255)
COLOR_BACKGROUND = (45, 46, 47) # HTML-Farbe #2d2e2f
COLOR_WHITE = (255, 255, 255)
COLOR_GOLD = (131, 188, 214) # BGR für Habegger-Gold
# Font & Logo
FONT_PATH = os.path.join("fonts", "Tisa Sans Pro Regular.ttf"
"") # Alternativ: Georgia.ttf, falls vorhanden
FONT_SIZE = 48
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
logo_path = "images/habegger_logo_goldbar_whitefont.png" # Logo mit sichtbarem Text
logo_pil = Image.open(logo_path).convert("RGBA")
logo_pil.thumbnail((300, 100))
logo_pil = Image.alpha_composite(Image.new("RGBA", logo_pil.size, (0, 0, 0, 0)), logo_pil)
logo_img = np.array(logo_pil)
logo_img = cv2.cvtColor(logo_img, cv2.COLOR_RGBA2BGRA)
def draw_unicode_text(cv2_image, text, position, color=COLOR_WHITE):
img_pil = Image.fromarray(cv2_image)
draw = ImageDraw.Draw(img_pil)
draw.text(position, text, font=font, fill=color)
return np.array(img_pil)
def parse_qr_data(data):
from urllib.parse import urlparse, parse_qs
if "?code=" in data:
parsed = urlparse(data)
query = parse_qs(parsed.query)
code = query.get("code", [""])[0]
return code
return None
def start_game(mail, name):
with open("last_user.txt", "w", encoding="utf-8-sig") as f:
f.write(f"{mail}\n{name}")
subprocess.run(["python", "loader_game.py"])
# Endlosschleife für den Scanner
while True:
detected_color = COLOR_GOLD
detected_time = None
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
# Rahmenfarbe wird später im Loop anhand der Zeit gesetzt
last_display_text = ""
start_timer = None
game_started = False
while True:
success, frame = cap.read()
if not success:
break
decoded_objects = decode(frame)
if not decoded_objects:
inverted_frame = cv2.bitwise_not(frame)
decoded_objects = decode(inverted_frame)
# QR-Status zurücksetzen nach 5 Sek.
if detected_time and time.time() - detected_time >= 5.0:
detected_color = COLOR_GOLD
frame_color = COLOR_GOLD
if decoded_objects:
detected_time = time.time()
for obj in decoded_objects:
points = obj.polygon
if len(points) > 4:
hull = cv2.convexHull(np.array([point for point in points], dtype=np.float32))
points = hull.reshape(-1, 2)
for i in range(len(points)):
pt1 = tuple(points[i])
pt2 = tuple(points[(i + 1) % len(points)])
cv2.line(frame, pt1, pt2, COLOR_GREEN, 3)
data = obj.data.decode("utf-8")
code = parse_qr_data(data)
if not code:
detected_color = COLOR_YELLOW
detected_time = time.time()
last_display_text = "Kein gültiger QR-Code erkannt."
break
try:
res = requests.post(API_URL, json={"code": code, "auth": API_SECRET}, timeout=5)
print("RAW Response:", res.text)
result = res.json()
except Exception as e:
detected_color = COLOR_YELLOW
detected_time = time.time()
last_display_text = f"API Fehler: {e}"
break
if not result.get("valid"):
detected_color = COLOR_YELLOW
last_display_text = "Ungültiger Code."
elif result.get("registered"):
name = result.get("name", "Unbekannt")
detected_color = COLOR_GREEN
last_display_text = f"Hallo {name}!"
start_timer = time.time()
game_started = True
last_mail = code
last_name = name
else:
detected_color = COLOR_YELLOW
last_display_text = "QR gültig, aber nicht registriert."
detected_time = time.time()
break
if start_timer and time.time() - start_timer >= 5.0:
cap.release()
cv2.destroyAllWindows()
start_game(last_mail, last_name)
break
display_frame = np.full((window_height, window_width, 3), COLOR_BACKGROUND, dtype=np.uint8)
cam_h, cam_w = frame.shape[:2]
x_offset = (window_width - cam_w) // 2
y_offset = (window_height - cam_h) // 2
display_frame[y_offset:y_offset+cam_h, x_offset:x_offset+cam_w] = frame
display_frame = draw_unicode_text(display_frame,
"Scanne deinen QR-Code um das Spiel zu starten.",
(80, 100), COLOR_WHITE)
if last_display_text:
display_frame = draw_unicode_text(display_frame, last_display_text,
(80, window_height - 100), COLOR_WHITE)
# Kamera-Rahmen
if detected_time and time.time() - detected_time < 5.0:
current_color = detected_color
else:
current_color = COLOR_GOLD
cv2.rectangle(display_frame, (x_offset, y_offset),
(x_offset + cam_w, y_offset + cam_h), current_color, 6)
# Logo oben rechts ohne Alpha-Blending-Probleme
logo_pos = (window_width - logo_img.shape[1] - 40, 40)
overlay = np.zeros_like(display_frame, dtype=np.uint8)
overlay[logo_pos[1]:logo_pos[1]+logo_img.shape[0], logo_pos[0]:logo_pos[0]+logo_img.shape[1]] = logo_img[:, :, :3]
mask = logo_img[:, :, 3] # alpha channel
for c in range(3):
display_frame[logo_pos[1]:logo_pos[1]+logo_img.shape[0], logo_pos[0]:logo_pos[0]+logo_img.shape[1], c] = (
logo_img[:, :, c] * (mask / 255.0) + display_frame[logo_pos[1]:logo_pos[1]+logo_img.shape[0], logo_pos[0]:logo_pos[0]+logo_img.shape[1], c] * (1.0 - mask / 255.0)).astype(np.uint8)
cv2.namedWindow("QR Scanner", cv2.WINDOW_NORMAL)
cv2.setWindowProperty("QR Scanner", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
cv2.imshow("QR Scanner", display_frame)
key = cv2.waitKey(1)
if key == 27 or key == ord('q'):
cap.release()
cv2.destroyAllWindows()
exit()

160
qr_scan_old.py Normal file
View File

@@ -0,0 +1,160 @@
import cv2
from pyzbar.pyzbar import decode
import time
import numpy as np
from urllib.parse import urlparse, parse_qs, unquote
from PIL import ImageFont, ImageDraw, Image
import os
import subprocess
# Fenstergröße & Farben
window_width, window_height = 1920, 1080
# Habegger Gold
COLOR_GREEN = (0, 255, 0)
COLOR_YELLOW = (0, 255, 255)
COLOR_BACKGROUND = (45, 46, 47) # HTML-Farbe #2d2e2f
COLOR_WHITE = (255, 255, 255)
COLOR_GOLD = (131, 188, 214) # BGR für Habegger-Gold
# Font & Logo
FONT_PATH = os.path.join("fonts", "Tisa Sans Pro Regular.ttf"
"") # Alternativ: Georgia.ttf, falls vorhanden
FONT_SIZE = 48
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
logo_path = "images/habegger_logo_goldbar_whitefont.png" # Logo mit sichtbarem Text
logo_pil = Image.open(logo_path).convert("RGBA")
logo_pil.thumbnail((300, 100))
logo_pil = Image.alpha_composite(Image.new("RGBA", logo_pil.size, (0, 0, 0, 0)), logo_pil)
logo_img = np.array(logo_pil)
logo_img = cv2.cvtColor(logo_img, cv2.COLOR_RGBA2BGRA)
def draw_unicode_text(cv2_image, text, position, color=COLOR_WHITE):
img_pil = Image.fromarray(cv2_image)
draw = ImageDraw.Draw(img_pil)
draw.text(position, text, font=font, fill=color)
return np.array(img_pil)
def parse_qr_data(data):
try:
parsed = urlparse(data)
query = parse_qs(parsed.query)
mail = query.get("mail", [""])[0]
name = query.get("name", [""])[0]
name = unquote(name, encoding="utf-8", errors="replace")
name = (name.replace("", "ö")
.replace("ü", "ü")
.replace("ä", "ä")
.replace("Ö", "Ö")
.replace("Ü", "Ü")
.replace("Ãß", "ß"))
if "@habegger.ch" in mail.lower() and name.strip():
return mail.strip(), name.strip()
except:
pass
return None, None
def start_game(mail, name):
with open("last_user.txt", "w", encoding="utf-8-sig") as f:
f.write(f"{mail}\n{name}")
subprocess.run(["python", "loader_game.py"])
# Endlosschleife für den Scanner
while True:
detected_color = COLOR_GOLD
detected_time = None
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
# Rahmenfarbe wird später im Loop anhand der Zeit gesetzt
last_display_text = ""
start_timer = None
game_started = False
while True:
success, frame = cap.read()
if not success:
break
decoded_objects = decode(frame)
# QR-Status zurücksetzen nach 5 Sek.
if detected_time and time.time() - detected_time >= 5.0:
detected_color = COLOR_GOLD
frame_color = COLOR_GOLD
if decoded_objects:
detected_time = time.time()
for obj in decoded_objects:
points = obj.polygon
if len(points) > 4:
hull = cv2.convexHull(np.array([point for point in points], dtype=np.float32))
points = hull.reshape(-1, 2)
for i in range(len(points)):
pt1 = tuple(points[i])
pt2 = tuple(points[(i + 1) % len(points)])
cv2.line(frame, pt1, pt2, COLOR_GREEN, 3)
data = obj.data.decode("utf-8")
mail, name = parse_qr_data(data)
if mail and name:
detected_color = COLOR_GREEN
last_display_text = f"Richtiger QR-Code: Hallo {name}"
detected_time = time.time()
start_timer = time.time()
game_started = True
last_mail = mail
last_name = name
else:
detected_color = COLOR_YELLOW
detected_time = time.time()
last_display_text = "Bitte dein HAG Last-Thursday Code scannen."
break
if start_timer and time.time() - start_timer >= 5.0:
cap.release()
cv2.destroyAllWindows()
start_game(last_mail, last_name)
break
display_frame = np.full((window_height, window_width, 3), COLOR_BACKGROUND, dtype=np.uint8)
cam_h, cam_w = frame.shape[:2]
x_offset = (window_width - cam_w) // 2
y_offset = (window_height - cam_h) // 2
display_frame[y_offset:y_offset+cam_h, x_offset:x_offset+cam_w] = frame
display_frame = draw_unicode_text(display_frame,
"Scanne deinen QR-Code um das Spiel zu starten.",
(80, 100), COLOR_WHITE)
if last_display_text:
display_frame = draw_unicode_text(display_frame, last_display_text,
(80, window_height - 100), COLOR_WHITE)
# Kamera-Rahmen
if detected_time and time.time() - detected_time < 5.0:
current_color = detected_color
else:
current_color = COLOR_GOLD
cv2.rectangle(display_frame, (x_offset, y_offset),
(x_offset + cam_w, y_offset + cam_h), current_color, 6)
# Logo oben rechts ohne Alpha-Blending-Probleme
logo_pos = (window_width - logo_img.shape[1] - 40, 40)
overlay = np.zeros_like(display_frame, dtype=np.uint8)
overlay[logo_pos[1]:logo_pos[1]+logo_img.shape[0], logo_pos[0]:logo_pos[0]+logo_img.shape[1]] = logo_img[:, :, :3]
mask = logo_img[:, :, 3] # alpha channel
for c in range(3):
display_frame[logo_pos[1]:logo_pos[1]+logo_img.shape[0], logo_pos[0]:logo_pos[0]+logo_img.shape[1], c] = (
logo_img[:, :, c] * (mask / 255.0) + display_frame[logo_pos[1]:logo_pos[1]+logo_img.shape[0], logo_pos[0]:logo_pos[0]+logo_img.shape[1], c] * (1.0 - mask / 255.0)).astype(np.uint8)
cv2.namedWindow("QR Scanner", cv2.WINDOW_NORMAL)
cv2.setWindowProperty("QR Scanner", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
cv2.imshow("QR Scanner", display_frame)
key = cv2.waitKey(1)
if key == 27 or key == ord('q'):
cap.release()
cv2.destroyAllWindows()
exit()

BIN
roll.ogg Normal file

Binary file not shown.

BIN
zip.ogg Normal file

Binary file not shown.