test webhook
This commit is contained in:
1 parent
940a4e5ad2
commit
ad80b1a814
23 files changed
+1007
No files matched your search
@@ -0,0 +1,3 @@
|
|||||||
|
python.exe -m pip install --upgrade pip
|
||||||
|
|
||||||
|
pip install pillow pyzbar opencv-python numpy pygame pygetwindow requests pyautogui
|
||||||
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
@@ -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
@@ -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
@@ -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