Compare commits

...

2 Commits

Author SHA1 Message Date
ad80b1a814 test webhook 2025-09-22 19:18:01 +02:00
940a4e5ad2 test webhook 2025-09-22 19:17:08 +02:00
50 changed files with 1007 additions and 1244 deletions

3
INSTALL_README.txt Normal file
View File

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

View File

@@ -1,9 +0,0 @@
MIT License
Copyright (c) 2025 putschgi
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

430
PDA.tmpl
View File

@@ -1,430 +0,0 @@
<html lang="en-us"><script src="{{cookiecutter.cdn}}pythons.js" type=module id="site" data-python="python{{cookiecutter.PYBUILD}}" data-LINES=42 data-COLUMNS=132 data-os="vtx,fs,snd,gui" async defer>#<!--
print("""
Loading {{cookiecutter.title}} from {{cookiecutter.archive}}.apk
Pygbag Version : {{cookiecutter.version}}
Template Version : 0.9.2
Python : {{cookiecutter.PYBUILD}}
CDN URL : {{cookiecutter.cdn}}
Screen : {{cookiecutter.width}}x{{cookiecutter.height}}
Title : {{cookiecutter.title}}
Folder : {{cookiecutter.directory}}
Authors : {{cookiecutter.authors}}
SPDX-License-Identifier: {{cookiecutter.spdx}}
""")
import sys
import asyncio
import platform
import json
from pathlib import Path
# screen pixels (real, hardware)
WIDTH=1024 # {{cookiecutter.width}}
HEIGHT=600 # {{cookiecutter.height}}
# reference/idealized screen pixels
REFX = 1980
REFY = 1080
def u(real, ref, v):
if abs(v)<0.9999999:
result = int( (float(real)/100.0) * (v*1000))
if v<0:
return real-result
return result
return int( (real/ref) * v )
def ux(*argv):
global WIDTH, REFX
acc = 0
for v in argv:
acc += u(WIDTH, REFX, v)
return acc
def uy(*argv):
global HEIGHT, REFY
acc = 0
for v in argv:
acc += u(HEIGHT, REFY, v)
return acc
# do not rename
async def custom_site():
import embed
platform.document.body.style.background = "#7f7f7f"
platform.window.transfer.hidden = true
platform.window.canvas.style.visibility = "visible"
apk = "{{cookiecutter.archive}}.apk"
bundle = "{{cookiecutter.archive}}"
# the C or js loader could do that but be explicit.
appdir = Path(f"/data/data/{bundle}") # /data/data/{{cookiecutter.archive}}
appdir.mkdir()
# mount apk
cfg = {
"io": "url",
"type":"mount",
"mount" : {
"point" : appdir.as_posix(),
"path" : "/",
}
}
track = platform.window.MM.prepare(apk, json.dumps(cfg))
marginx = ux(.020) # 20%
marginy = uy(.045) # 45%
print(" /////////////////////////////////////////////////////////: ")
# wait until zip mount + overlayfs is complete
while not track.ready:
await asyncio.sleep(.1)
# preloader will change dir and prepend it to sys.path
platform.run_main(PyConfig, loaderhome= appdir / "assets", loadermain=None)
# wait preloading complete
# that includes images and wasm compilation of bundled modules
while embed.counter()<0:
await asyncio.sleep(.1)
main = appdir / "assets" / "main.py"
# test/wait user media interaction
# Starte sofort, ohne auf User Interaktion zu warten
platform.window.MM.UME = True
# start async top level machinery if not started and add a console in any case if requested.
await TopLevel_async_handler.start_toplevel(platform.shell, console=window.python.config.debug)
# now that apk is mounted we have access to font cache
# but we need to fill __file__/__name__ that are not yet set
__import__(__name__).__file__ = main
def ui_callback(pkg):
print(f"installing {pkg}")
await shell.source(main, callback=ui_callback)
# if you don't reach that step
# your main.py has an infinite sync loop somewhere !
print("noctxl.html: ready")
shell.interactive()
asyncio.run( custom_site() )
# BEGIN BLOCK
#
# now this is the html part you can (and should) customize
# It is not mandatory : pygame-script when it reads the first line (also called
# shebang ) of above code create absolute minimal widget set
# required for running with default rules
#
# do not alter that comment block it is separating python code from html code
# =============================================================================
# --></script><head><!--
//=============================================================================
//
//
//
//
//
//
//
{%- if cookiecutter.comment != "" -%}
{{cookiecutter.comment}}
{% endif %}
--><script type="application/javascript">
// END BLOCK
// this dict is available under PyConfig.config from __main__
config = {
xtermjs : "{{cookiecutter.xtermjs}}" ,
_sdl2 : "canvas",
user_canvas : 0,
user_canvas_managed : 0,
ume_block : {{cookiecutter.ume_block}},
can_close : {{cookiecutter.can_close}},
archive : "{{cookiecutter.archive}}",
gui_debug : 3,
cdn : "{{cookiecutter.cdn}}",
autorun : {{cookiecutter.autorun}},
PYBUILD : "{{cookiecutter.PYBUILD}}"
}
</script>
<title>{{cookiecutter.title}}</title>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="height=device-height, initial-scale=1.0">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes"/>
<link rel="icon" type="image/png" href="favicon.png" sizes="16x16">
<style>
#status {
display: inline-block;
vertical-align: top;
margin-top: 20px;
margin-left: 30px;
font-weight: bold;
color: rgb(120, 120, 120);
}
#progress {
height: 20px;
width: 300px;
}
div.emscripten { text-align: center; }
div.emscripten_border { border: 1px solid black; }
div.thick_border { border: 4px solid black; }
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
/* average size of droid screen 470dp x 320dp */
canvas.emscripten {
border: 0px none;
background-color: transparent;
width: 100%;
height: 100%;
z-index: 5;
padding: 0;
margin: 0 auto;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
body {
font-family: arial;
margin: 0;
padding: none;
background-color:powderblue;
}
.topright{
position:absolute;
top:0px;
right:0px;
}
.bottomright {
position:absolute;
top: 40%;
right: 0px;
}
.center {
display: flex;
align-items: center;
justify-content: center;
}
.trinfo{
position: relative;
right: 0px;
border: 1px solid black;
}
.framed{
position: relative;
top: 150px;
right: 10px;
border: 1px solid black;
}
</style>
<script>
/*
if (navigator.serviceWorker)
navigator.serviceWorker.register("{{cookiecutter.cdn}}pygbag{{cookiecutter.version}}.js")
else
console.warn("Service workers not supported")
*/
</script>
<script src="{{cookiecutter.cdn}}/browserfs.min.js"></script>
</head>
<body>
<div id="transfer" align=center>
<!-- <div class="spinner" id='spinner'></div> -->
<div class="emscripten" id="status">Downloading...</div>
<div class="emscripten">
<progress value="0" max="100" id="progress"></progress>
</div>
</div>
<canvas class="emscripten" id="canvas"
width="1px"
height="1px"
oncontextmenu="event.preventDefault()" tabindex=1>
</canvas>
<div id=html></div>
<div id=crt class=bottomright >
<div id="system" hidden>
<div class="button-container">
<button id="aiostop" disabled>AIO ⏏︎</button>
<button id="aiopaused_true" disabled>AIO ■</button>
<button id="aiopaused_false" disabled>AIO ▶</button>
<button id="pygame_mixer_music_pause" disabled>Music ■</button>
</div>
<div class="button-container">
<div id=load_min>min</div>
<div id=load_avg>avg</div>
<div id=load_max>max</div>
<button id="load_rst" disabled>RESET</button>
</div>
<div id="level">(battery level unknown)</div>
<div id="stateBattery">(charging state unknown)</div>
</div>
<div id=box class="emscripten_border" hidden=true>
<div id="info" class="trinfo"></div>
<iframe id="iframe" class="framed" name="iframe"
width="470px" height="90%"
allowtransparency="true"
style="z-index: 10;"
style="background: #FFFFFF;"
frameborder="1"
allowfullscreen="true"
webkitallowfullscreen="true"
msallowfullscreen="true"
mozallowfullscreen="true"
sandbox="allow-same-origin allow-top-navigation allow-scripts allow-pointer-lock"
allow="autoplay; fullscreen *; geolocation; microphone; camera; midi; monetization; xr-spatial-tracking; gamepad; gyroscope; accelerometer; xr; cross-origin-isolated"
src="{{cookiecutter.cdn}}empty.html"
scrolling="yes">
</iframe>
</div>
</div>
<div id="dlg" hidden>
<input type="file" id="dlg_multifile" multiple accept="image/*">
<label for="dlg_multifile">Select files</label>
</div>
<div id="pyconsole">
<div id="terminal" tabIndex=1 align="left"></div>
</div>
<script type="application/javascript">
// Entfernt alle beforeunload-Warnungen
window.onbeforeunload = null;
globalThis.__canvas_resized = (self, ecw, ech) => {
console.warn("TODO: panda3d canvas monitor", self, ecw, ech)
}
async function custom_onload(debug_hidden) {
// this is called before anythinh python is loaded
// make your js customization here
console.log(__FILE__, "custom_onload")
pyconsole.hidden = debug_hidden
system.hidden = debug_hidden
transfer.hidden = debug_hidden
info.hidden = debug_hidden
box.hidden = debug_hidden
}
function custom_prerun(){
// no python main and no (MEMFS + VFS) yet.
console.log(__FILE__, "custom_prerun")
}
function custom_postrun(){
// python main and no VFS filesystem yet.
console.log(__FILE__, "custom_postrun")
}
function debug() {
// allow to gain access to dev tools from js console
// but only on desktop. difficult to reach when in iframe
python.config.debug = true
custom_onload(false)
Module.PyRun_SimpleString("shell.uptime()")
window_resize()
}
function info_inline(data){
document.getElementById("info").innerHTML = data
}
function info_online(url) {
// display info about current APK
fetch( url /*, options */)
.then((response) => response.text())
.then((html) => {
info_inline(html);
})
.catch((error) => {
console.warn(error);
});
}
function frame_online(url) {
window.frames["iframe"].location = url;
}
</script>
</body>
</html>

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.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 233 KiB

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

After

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 KiB

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 419 KiB

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 886 KiB

After

Width:  |  Height:  |  Size: 794 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 794 KiB

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

805
main.py
View File

@@ -1,805 +0,0 @@
import asyncio
import pygame
import random
import sys
import os
import time
import re
random.seed(time.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 = {}
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 False
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 probleeeeem")
return False
return False
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
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
def is_UP_event(event):
# Tastatur oder Joystick oben
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():
await asyncio.sleep(0)
result = touch(event)
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif 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 = re.split(r"\r?\n\r?\n", f.read().strip())
print(f"Anzahl erkannter Sets: {len(sets)}")
index = random.randint(0, len(sets) - 1)
print(f"Zufällig gewählter Index: {index}")
chosen_raw = sets[index].strip()
print("Erste Zeilen des gewählten Sets:")
print("\n".join(chosen_raw.splitlines()[:3])) # Zeigt max. 3 Zeilen an
chosen = chosen_raw.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 Exception as e:
print("Fehler beim Laden der Sequenz:", e)
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)}%. Drücke die Leertaste um deine Punkte zu übermitteln."
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 is_SPACE_event(event) or (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 = touch(event)
elif event.type == pygame.FINGERDOWN and not touch_start:
touch(event)
print(result)
if 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 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 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())

188
qr_scan.py Normal file
View File

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

160
qr_scan_old.py Normal file
View File

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

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.