test webhook
This commit is contained in:
3
INSTALL_README.txt
Normal file
3
INSTALL_README.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
python.exe -m pip install --upgrade pip
|
||||||
|
|
||||||
|
pip install pillow pyzbar opencv-python numpy pygame pygetwindow requests pyautogui
|
||||||
BIN
fonts/Tisa Sans Pro Regular.ttf
Normal file
BIN
fonts/Tisa Sans Pro Regular.ttf
Normal file
Binary file not shown.
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Black Italic.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Black Italic.ttf
Normal file
Binary file not shown.
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Black.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Black.ttf
Normal file
Binary file not shown.
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Bold Italic.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Bold Italic.ttf
Normal file
Binary file not shown.
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Bold.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Bold.ttf
Normal file
Binary file not shown.
BIN
fonts/tisa-sans-pro/Tisa Sans Pro ExtraBold Italic.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro ExtraBold Italic.ttf
Normal file
Binary file not shown.
BIN
fonts/tisa-sans-pro/Tisa Sans Pro ExtraBold.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro ExtraBold.ttf
Normal file
Binary file not shown.
BIN
fonts/tisa-sans-pro/Tisa Sans Pro ExtraLight Italic.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro ExtraLight Italic.ttf
Normal file
Binary file not shown.
BIN
fonts/tisa-sans-pro/Tisa Sans Pro ExtraLight.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro ExtraLight.ttf
Normal file
Binary file not shown.
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Italic.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Italic.ttf
Normal file
Binary file not shown.
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Light Italic.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Light Italic.ttf
Normal file
Binary file not shown.
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Light.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Light.ttf
Normal file
Binary file not shown.
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Medium Italic.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Medium Italic.ttf
Normal file
Binary file not shown.
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Medium.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Medium.ttf
Normal file
Binary file not shown.
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Regular.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Regular.ttf
Normal file
Binary file not shown.
656
loader_game.py
Normal file
656
loader_game.py
Normal 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()
|
||||||
188
qr_scan.py
Normal file
188
qr_scan.py
Normal 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
160
qr_scan_old.py
Normal 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()
|
||||||
Reference in New Issue
Block a user