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

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