794 lines
29 KiB
Python
794 lines
29 KiB
Python
import asyncio
|
|
import pygame
|
|
import random
|
|
import sys
|
|
import os
|
|
import time
|
|
|
|
|
|
from pygame import FULLSCREEN
|
|
|
|
# === Konstanten ===
|
|
SCREEN_WIDTH = 1920
|
|
SCREEN_HEIGHT = 1080
|
|
FPS = 60
|
|
|
|
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 = 3
|
|
CASE_SPEED_FAST = 8
|
|
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
|
|
|
|
# === Globale Variablen ===
|
|
current_case_visible = True
|
|
transition_fps = 60
|
|
transition_counter = 0
|
|
collision_x = TRAILER_X
|
|
stacked_cases = []
|
|
case_sequence = []
|
|
case_index = 0
|
|
current_case = None
|
|
next_queue = []
|
|
running = True
|
|
can_use_tilt = True
|
|
can_use_on_top = True
|
|
game_finished = False
|
|
shake_timer = 0
|
|
state = 1 # PLAYING
|
|
prepared_form = None
|
|
ready_to_submit = False
|
|
touch_start = None
|
|
touch_start_time = None
|
|
|
|
def init_sounds():
|
|
global sound_fail, sound_place, sound_roll, sound_tut1, sound_tut2, sound_tuuut, zip_sound
|
|
pygame.mixer.init()
|
|
sound_fail = pygame.mixer.Sound("sounds/fail.ogg")
|
|
sound_fail.set_volume(0.6)
|
|
sound_place = pygame.mixer.Sound("sounds/place.ogg")
|
|
sound_place.set_volume(0.6)
|
|
sound_roll = pygame.mixer.Sound("sounds/roll.ogg")
|
|
sound_roll.set_volume(0.3)
|
|
sound_tut1 = pygame.mixer.Sound("sounds/tut1.ogg")
|
|
sound_tut1.set_volume(1)
|
|
sound_tut2 = pygame.mixer.Sound("sounds/tut2.ogg")
|
|
sound_tut2.set_volume(1)
|
|
sound_tuuut = pygame.mixer.Sound("sounds/tuuut.ogg")
|
|
sound_tuuut.set_volume(1)
|
|
zip_sound = pygame.mixer.Sound("sounds/zip.ogg")
|
|
zip_sound.set_volume(1)
|
|
print("Sounds erfolgreich geladen.")
|
|
|
|
case_images = {}
|
|
|
|
async def touch(event):
|
|
global touch_start, touch_start_time
|
|
|
|
if event.type == pygame.FINGERDOWN and not touch_start:
|
|
touch_start = (event.x * SCREEN_WIDTH, event.y * SCREEN_HEIGHT)
|
|
touch_start_time = time.time()
|
|
print("touch_start")
|
|
return None
|
|
|
|
elif event.type == pygame.FINGERUP and touch_start:
|
|
end = (event.x * SCREEN_WIDTH, event.y * SCREEN_HEIGHT)
|
|
duration = time.time() - touch_start_time
|
|
dx = end[0] - touch_start[0]
|
|
dy = end[1] - touch_start[1]
|
|
print("touch_end")
|
|
print(f"Touch dx={dx}, dy={dy}, duration={duration}")
|
|
|
|
touch_start = None
|
|
touch_start_time = None
|
|
|
|
if abs(dx) < 30 and abs(dy) < 30 and duration < 0.1:
|
|
return "snap"
|
|
elif dx < -50 and abs(dy) < 80 and duration > 0.08:
|
|
return "tilt"
|
|
elif dy < -50 and abs(dx) < 80 and duration > 0.08:
|
|
return "ontop"
|
|
else:
|
|
print("touch problem")
|
|
return None
|
|
|
|
|
|
return None
|
|
|
|
async def is_SPACE_event(event):
|
|
|
|
# Tastatur oder Joystick A
|
|
if (event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE) or \
|
|
(event.type == pygame.JOYBUTTONDOWN and event.button == 0):
|
|
return True
|
|
|
|
# Mausklick (nur Linksklick, für Desktop-Tests)
|
|
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
|
|
return True
|
|
|
|
return False
|
|
|
|
async def is_LEFT_event(event):
|
|
# Tastatur oder Joystick links
|
|
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)):
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
async def is_UP_event(event):
|
|
global touch_start, touch_start_time
|
|
|
|
if ((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)):
|
|
return True
|
|
|
|
return False
|
|
|
|
async def init_game():
|
|
global screen, clock, font, controls
|
|
pygame.init()
|
|
pygame.joystick.init()
|
|
init_sounds()
|
|
|
|
if pygame.joystick.get_count() > 0:
|
|
joystick = pygame.joystick.Joystick(0)
|
|
joystick.init()
|
|
print(f"Controller erkannt: {joystick.get_name()}")
|
|
controls = "tutorial_controller"
|
|
else:
|
|
controls = "tutorial"
|
|
print("Kein Controller gefunden.")
|
|
|
|
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
|
|
pygame.display.set_caption("Habegger Loader Game")
|
|
clock = pygame.time.Clock()
|
|
font = pygame.font.SysFont(None, 48)
|
|
|
|
class Case:
|
|
def __init__(self, size=None):
|
|
global case_index
|
|
self.rotated = False
|
|
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
|
|
self.rotated = False # <==== WICHTIG: HINZUFÜGEN!!!
|
|
global current_case_visible
|
|
current_case_visible = True
|
|
|
|
def move(self):
|
|
global collision_x
|
|
if not self.placed and not pygame.mixer.Channel(1).get_busy():
|
|
if sound_roll:
|
|
pygame.mixer.Channel(1).play(sound_roll, loops=-1)
|
|
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
|
|
|
|
async def fail_snap(self):
|
|
global collision_x
|
|
steps = 5
|
|
bounce_distance = 20 # Pixel zurückspringen
|
|
|
|
for _ in range(steps):
|
|
self.x += bounce_distance / steps
|
|
draw_game()
|
|
pygame.display.flip()
|
|
await asyncio.sleep(0)
|
|
|
|
self.placed = True
|
|
self.snapped = True
|
|
collision_x = self.x + self.width
|
|
|
|
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 collides_with_barrier(self):
|
|
return not self.snapped and self.x <= collision_x
|
|
|
|
def draw(self, surface=None):
|
|
global screen
|
|
surface = surface or screen
|
|
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))
|
|
async def animate_snap(self):
|
|
if sound_roll:
|
|
pygame.mixer.Channel(1).stop()
|
|
global collision_x
|
|
target_x = collision_x
|
|
steps = 10
|
|
delta = (self.x - target_x) / steps
|
|
for _ in range(steps):
|
|
self.x -= delta
|
|
draw_game()
|
|
pygame.display.flip()
|
|
await asyncio.sleep(0)
|
|
self.x = target_x
|
|
self.placed = True
|
|
self.snapped = True
|
|
collision_x = self.x + self.width
|
|
|
|
async 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()
|
|
await asyncio.sleep(0)
|
|
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
|
|
|
|
async def animate_place_on_top(self):
|
|
if sound_roll:
|
|
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()
|
|
await asyncio.sleep(0)
|
|
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()
|
|
await asyncio.sleep(0)
|
|
self.x = target_x
|
|
self.placed = True
|
|
self.snapped = True
|
|
collision_x = self.x + self.width
|
|
return True
|
|
current_case_visible = True
|
|
return False
|
|
|
|
def load_case_images():
|
|
global 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()
|
|
|
|
async def show_start_sequence():
|
|
global zip_sound
|
|
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):
|
|
await asyncio.sleep(0)
|
|
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.play()
|
|
duration = 200
|
|
steps = 60
|
|
for step in range(steps + 1):
|
|
await asyncio.sleep(0)
|
|
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)
|
|
|
|
def load_instruction_image():
|
|
global controls
|
|
try:
|
|
image = pygame.image.load("images/"+controls+".png")
|
|
return pygame.transform.scale(image, (SCREEN_WIDTH, SCREEN_HEIGHT))
|
|
except:
|
|
return None
|
|
|
|
def load_background():
|
|
try:
|
|
image = pygame.image.load("images/background.png")
|
|
return pygame.transform.scale(image, (SCREEN_WIDTH, GAME_AREA_HEIGHT))
|
|
except:
|
|
return None
|
|
|
|
def draw_game(hide_trailer=False):
|
|
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))
|
|
title_font = pygame.font.SysFont(None, 64)
|
|
title_surface = title_font.render("Habegger Loader Game", True, (255, 255, 255))
|
|
screen.blit(title_surface, (50, 50))
|
|
|
|
# === Ladebalken zeichnen ===
|
|
percent_full = calculate_load_percent()
|
|
bar_width = 400
|
|
bar_height = 30
|
|
bar_x = SCREEN_WIDTH - bar_width - 50
|
|
bar_y = 50
|
|
pygame.draw.rect(screen, (80, 80, 80), (bar_x, bar_y, bar_width, bar_height))
|
|
pygame.draw.rect(screen, (0, 255, 0), (bar_x, bar_y, bar_width * (percent_full / 100), bar_height))
|
|
bar_font = pygame.font.SysFont(None, 36)
|
|
bar_text = bar_font.render(f"{int(percent_full)}% geladen", True, (255, 255, 255))
|
|
screen.blit(bar_text, (bar_x + bar_width // 2 - bar_text.get_width() // 2, bar_y + bar_height // 2 - bar_text.get_height() // 2))
|
|
|
|
# === Vorschau nächstes Case (nur Text) ===
|
|
preview_box_width = 150
|
|
preview_box_height = 80
|
|
preview_box_x = (SCREEN_WIDTH - preview_box_width) // 2
|
|
preview_box_y = SCREEN_HEIGHT - preview_box_height - 20
|
|
|
|
# Grauer Hintergrundkasten
|
|
pygame.draw.rect(screen, (80, 80, 80), (preview_box_x, preview_box_y, preview_box_width, preview_box_height),
|
|
border_radius=12)
|
|
|
|
# Überschrift: "B x H"
|
|
title_font = pygame.font.SysFont(None, 28)
|
|
title_surface = title_font.render("Next: (B x H)", True, (200, 200, 200))
|
|
title_x = preview_box_x + (preview_box_width - title_surface.get_width()) // 2
|
|
title_y = preview_box_y + 5
|
|
screen.blit(title_surface, (title_x, title_y))
|
|
|
|
# Text für nächstes Case (z.B. "2x1")
|
|
if next_queue:
|
|
next_case = next_queue[0]
|
|
preview_text = f"{next_case['w']}x{next_case['h']}"
|
|
preview_font = pygame.font.SysFont(None, 48)
|
|
text_surface = preview_font.render(preview_text, True, (255, 255, 255))
|
|
text_x = preview_box_x + (preview_box_width - text_surface.get_width()) // 2
|
|
text_y = title_y + title_surface.get_height() + 5
|
|
screen.blit(text_surface, (text_x, text_y))
|
|
|
|
# === Echte Vorschau der nächsten 3 Cases (horizontal in Top-Bar) ===
|
|
preview_start_x = 50
|
|
preview_start_y = SCREEN_HEIGHT - 200
|
|
spacing_between_cases = 80 # Fester Abstand zwischen den Vorschau-Cases
|
|
title_next = title_font.render("Next 3:", True, (200, 200, 200))
|
|
title_x = 50
|
|
title_y = SCREEN_HEIGHT - 250
|
|
screen.blit(title_next, (title_x, title_y))
|
|
|
|
current_x = preview_start_x
|
|
|
|
for i, case_data in enumerate(next_queue[:3]):
|
|
preview_width = case_data['w'] * (UNIT_LENGTH // 2)
|
|
preview_height = case_data['h'] * (UNIT_HEIGHT // 2)
|
|
preview_case_surface = pygame.Surface((preview_width, preview_height), pygame.SRCALPHA)
|
|
|
|
# Bild laden oder Rechteck zeichnen
|
|
key = f"case_w{case_data['w']}_h{case_data['h']}"
|
|
image = case_images.get(key)
|
|
|
|
if image:
|
|
image = pygame.transform.scale(image, (preview_width, preview_height))
|
|
preview_case_surface.blit(image, (0, 0))
|
|
else:
|
|
pygame.draw.rect(preview_case_surface, (180, 180, 180), (0, 0, preview_width, preview_height))
|
|
|
|
preview_y = preview_start_y
|
|
|
|
# Blit auf Hauptscreen
|
|
screen.blit(preview_case_surface, (current_x, preview_y))
|
|
|
|
# Nächste X-Position anpassen
|
|
current_x += preview_width + spacing_between_cases
|
|
|
|
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)
|
|
|
|
async def show_instruction_screen(image):
|
|
if not image:
|
|
return
|
|
waiting = True
|
|
while waiting:
|
|
screen.blit(image, (0, 0))
|
|
pygame.display.flip()
|
|
await asyncio.sleep(0)
|
|
for event in pygame.event.get():
|
|
result = await touch(event)
|
|
if event.type == pygame.QUIT:
|
|
pygame.quit()
|
|
sys.exit()
|
|
elif await is_SPACE_event(event) or result == "snap":
|
|
waiting = False
|
|
|
|
async def fail_current_case():
|
|
global can_use_tilt, can_use_on_top, game_finished, shake_timer
|
|
if sound_fail:
|
|
sound_fail.play()
|
|
await 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
|
|
spawn_case_in_game()
|
|
shake_timer = 10
|
|
|
|
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 spawn_case_in_game():
|
|
global current_case, next_queue, case_index
|
|
if not case_sequence:
|
|
generate_fill_sequence()
|
|
|
|
while len(next_queue) < 3:
|
|
index = (case_index + len(next_queue)) % len(case_sequence)
|
|
next_queue.append(case_sequence[index])
|
|
|
|
if case_index >= len(case_sequence):
|
|
current_case = None
|
|
return
|
|
case_data = case_sequence[case_index % len(case_sequence)]
|
|
case_index += 1
|
|
|
|
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])
|
|
|
|
current_case = Case(size=case_data)
|
|
|
|
async def show_game_over(score):
|
|
import sys
|
|
import urllib.parse
|
|
|
|
try:
|
|
import js
|
|
except ImportError:
|
|
js = None
|
|
username = "Spieler"
|
|
|
|
try:
|
|
query = js.window.location.search
|
|
params = urllib.parse.parse_qs(query[1:])
|
|
username = params.get("name", ["Spieler"])[0]
|
|
except Exception:
|
|
pass
|
|
|
|
data = {
|
|
"name": username,
|
|
"score": int(score),
|
|
"api_key": "hag-trailer-8051"
|
|
}
|
|
|
|
def is_browser():
|
|
return sys.platform in ("emscripten", "wasi")
|
|
|
|
if is_browser():
|
|
global prepared_form, ready_to_submit
|
|
|
|
if not is_browser() or js is None:
|
|
return
|
|
|
|
try:
|
|
form = js.document.createElement("form")
|
|
form.method = "POST"
|
|
form.action = "https://hag-game.carabella.ch/submit_points.php"
|
|
|
|
for key, value in data.items():
|
|
input_field = js.document.createElement("input")
|
|
input_field.type = "hidden"
|
|
input_field.name = key
|
|
input_field.value = str(value)
|
|
form.appendChild(input_field)
|
|
|
|
js.document.body.appendChild(form)
|
|
|
|
prepared_form = form
|
|
ready_to_submit = True
|
|
|
|
print("Formular vorbereitet. Warten auf Bestätigung (SPACE oder Klick).")
|
|
|
|
except Exception as e:
|
|
print("Fehler beim Formular vorbereiten:", e)
|
|
|
|
|
|
else:
|
|
try:
|
|
import urllib.request
|
|
body = urllib.parse.urlencode(data).encode()
|
|
req = urllib.request.Request(
|
|
url="https://hag-game.carabella.ch/submit_points.php",
|
|
data=body,
|
|
method="POST",
|
|
headers={
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
}
|
|
)
|
|
with urllib.request.urlopen(req) as response:
|
|
if response.getcode() != 200:
|
|
raise Exception("HTTP error")
|
|
print("Punkte erfolgreich lokal gesendet!")
|
|
except Exception as e:
|
|
print("Fehler beim lokalen Senden:", e)
|
|
|
|
pygame.mixer.stop()
|
|
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
|
|
overlay.set_alpha(220)
|
|
overlay.fill((0, 0, 0))
|
|
screen.blit(overlay, (0, 0))
|
|
font_big = pygame.font.SysFont(None, 72)
|
|
message = f"Spiel beendet! Punkte: {int(score)}%"
|
|
text = font_big.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:
|
|
await asyncio.sleep(0)
|
|
for event in pygame.event.get():
|
|
if event.type == pygame.QUIT:
|
|
pygame.quit()
|
|
sys.exit()
|
|
elif await is_SPACE_event(event) or await touch(event) == "snap":
|
|
if ready_to_submit and prepared_form:
|
|
print("Benutzer bestätigt, Punkte werden jetzt gesendet.")
|
|
prepared_form.submit()
|
|
pygame.quit()
|
|
sys.exit()
|
|
|
|
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)
|
|
|
|
async def main():
|
|
global running, can_use_tilt, can_use_on_top, game_finished
|
|
global current_case, transition_counter, state
|
|
global screen, clock, font, instruction_image, background_image
|
|
global touch_start
|
|
|
|
await init_game()
|
|
load_case_images()
|
|
instruction_image = load_instruction_image()
|
|
background_image = load_background()
|
|
generate_fill_sequence()
|
|
|
|
await asyncio.sleep(1)
|
|
await show_instruction_screen(instruction_image)
|
|
await show_start_sequence()
|
|
spawn_case_in_game()
|
|
|
|
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:
|
|
await 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):
|
|
await show_game_over(calculate_load_percent())
|
|
game_finished = True
|
|
continue
|
|
|
|
clock.tick(FPS)
|
|
await asyncio.sleep(0)
|
|
|
|
if transition_counter > 0:
|
|
transition_counter -= 1
|
|
draw_game()
|
|
pygame.display.flip()
|
|
await asyncio.sleep(0)
|
|
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 or event.type == pygame.FINGERDOWN or touch_start or (event.type == pygame.MOUSEBUTTONDOWN and event.button == 1):
|
|
if state == 1: # PLAYING
|
|
result = "none"
|
|
if event.type == pygame.FINGERUP and touch_start:
|
|
result = await touch(event)
|
|
print(result)
|
|
elif event.type == pygame.FINGERDOWN and not touch_start:
|
|
await touch(event)
|
|
|
|
if await is_LEFT_event(event) or result == "ontop":
|
|
can_tip = current_case.length_units * UNIT_HEIGHT <= TRAILER_HEIGHT
|
|
if current_case and current_case.allow_snap and can_use_tilt and can_tip:
|
|
await current_case.animate_tip()
|
|
can_use_tilt = False
|
|
elif await is_UP_event(event) or result == "tilt":
|
|
if current_case and current_case.allow_snap and can_use_on_top and current_case.can_place_on_top():
|
|
if await current_case.animate_place_on_top():
|
|
transition_counter = transition_fps
|
|
if sound_place:
|
|
sound_place.play()
|
|
stacked_cases.append(current_case)
|
|
spawn_case_in_game()
|
|
can_use_tilt = True
|
|
can_use_on_top = True
|
|
else:
|
|
await current_case.fail_snap()
|
|
if sound_place:
|
|
sound_place.play()
|
|
stacked_cases.append(current_case)
|
|
spawn_case_in_game()
|
|
shake_timer = 10
|
|
elif await is_SPACE_event(event) or result == "snap":
|
|
if current_case and current_case.allow_snap:
|
|
await current_case.animate_snap()
|
|
can_use_tilt = True
|
|
can_use_on_top = True
|
|
if sound_place:
|
|
sound_place.play()
|
|
transition_counter = transition_fps
|
|
stacked_cases.append(current_case)
|
|
spawn_case_in_game()
|
|
|
|
if state == 1: # PLAYING
|
|
if current_case is None:
|
|
continue
|
|
if current_case:
|
|
current_case.move()
|
|
if current_case.collides_with_barrier():
|
|
await fail_current_case()
|
|
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:
|
|
await show_game_over(calculate_load_percent())
|
|
game_finished = True
|
|
continue
|
|
if calculate_load_percent() >= 99.9 and case_index >= len(case_sequence):
|
|
await show_game_over(100)
|
|
game_finished = True
|
|
continue
|
|
if not current_case.placed and current_case.collides_with_barrier():
|
|
await 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
|
|
if sound_fail:
|
|
sound_fail.play()
|
|
spawn_case_in_game()
|
|
shake_timer = 10
|
|
draw_game()
|
|
|
|
pygame.display.flip()
|
|
|
|
pygame.quit()
|
|
sys.exit()
|
|
|
|
# === Spielstart ===
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|