Compare commits
2 Commits
old
...
ad80b1a814
| Author | SHA1 | Date | |
|---|---|---|---|
| ad80b1a814 | |||
| 940a4e5ad2 |
3
INSTALL_README.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
python.exe -m pip install --upgrade pip
|
||||
|
||||
pip install pillow pyzbar opencv-python numpy pygame pygetwindow requests pyautogui
|
||||
9
LICENSE
@@ -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
@@ -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>
|
||||
BIN
fonts/Tisa Sans Pro Regular.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Black Italic.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Black.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Bold Italic.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Bold.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro ExtraBold Italic.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro ExtraBold.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro ExtraLight Italic.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro ExtraLight.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Italic.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Light Italic.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Light.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Medium Italic.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Medium.ttf
Normal file
BIN
fonts/tisa-sans-pro/Tisa Sans Pro Regular.ttf
Normal file
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 233 KiB After Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 212 KiB After Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 352 KiB After Width: | Height: | Size: 356 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 366 KiB After Width: | Height: | Size: 373 KiB |
|
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 194 KiB |
|
Before Width: | Height: | Size: 419 KiB After Width: | Height: | Size: 427 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 886 KiB After Width: | Height: | Size: 794 KiB |
|
Before Width: | Height: | Size: 794 KiB |
656
loader_game.py
Normal file
@@ -0,0 +1,656 @@
|
||||
import pygame
|
||||
import random
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import numpy as np
|
||||
import time
|
||||
|
||||
|
||||
SCREEN_WIDTH = 1920
|
||||
SCREEN_HEIGHT = 1080
|
||||
FPS = 120
|
||||
|
||||
TOP_BAR_HEIGHT = SCREEN_HEIGHT // 8
|
||||
GAME_AREA_HEIGHT = SCREEN_HEIGHT // 1.6
|
||||
BOTTOM_BAR_HEIGHT = SCREEN_HEIGHT // 4
|
||||
|
||||
TRAILER_WIDTH = int(SCREEN_WIDTH * 0.885416666667)
|
||||
TRAILER_HEIGHT = int(SCREEN_HEIGHT * 0.2778)
|
||||
TRAILER_X = (SCREEN_WIDTH - TRAILER_WIDTH) // 4
|
||||
TRAILER_Y = TOP_BAR_HEIGHT + 200
|
||||
|
||||
CASE_SPEED_SLOW = 2
|
||||
CASE_SPEED_FAST = 6
|
||||
CASE_COLORS = [(200, 0, 0), (0, 200, 0), (0, 0, 200)]
|
||||
SNAP_THRESHOLD = 60
|
||||
FAST_TO_SLOW_DISTANCE = 120
|
||||
|
||||
UNIT_LENGTH = TRAILER_HEIGHT // 3
|
||||
UNIT_HEIGHT = TRAILER_HEIGHT // 3
|
||||
|
||||
pygame.init()
|
||||
pygame.joystick.init()
|
||||
if pygame.joystick.get_count() > 0:
|
||||
joystick = pygame.joystick.Joystick(0)
|
||||
joystick.init()
|
||||
print(f"Controller erkannt: {joystick.get_name()}")
|
||||
else:
|
||||
print("Kein Controller gefunden.")
|
||||
pygame.mixer.init()
|
||||
sound_fail = pygame.mixer.Sound("fail.ogg")
|
||||
sound_fail.set_volume(0.6)
|
||||
sound_place = pygame.mixer.Sound("place.ogg")
|
||||
sound_place.set_volume(0.6)
|
||||
sound_roll = pygame.mixer.Sound("roll.ogg")
|
||||
sound_roll.set_volume(0.3)
|
||||
current_case_visible = True
|
||||
|
||||
# Animationssteuerung
|
||||
transition_fps = 60
|
||||
transition_counter = 0
|
||||
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
|
||||
|
||||
|
||||
|
||||
def generate_beep(frequency=440, duration_ms=500, volume=0.5):
|
||||
sample_rate = 44100
|
||||
n_samples = int(sample_rate * (duration_ms / 1000.0))
|
||||
t = np.linspace(0, duration_ms / 1000.0, n_samples, False)
|
||||
wave = np.sin(2 * np.pi * frequency * t) * volume
|
||||
audio = (wave * 32767).astype(np.int16)
|
||||
return pygame.sndarray.make_sound(np.column_stack([audio, audio]))
|
||||
|
||||
|
||||
sound_tut1 = generate_beep(440, 600)
|
||||
sound_tut2 = generate_beep(440, 400)
|
||||
sound_tuuut = generate_beep(600, 1000)
|
||||
pygame.display.set_caption("Habegger Loader Game")
|
||||
pygame.event.set_grab(True)
|
||||
clock = pygame.time.Clock()
|
||||
|
||||
font = pygame.font.SysFont(None, 48)
|
||||
|
||||
PLAYING = 1
|
||||
state = PLAYING
|
||||
shake_timer = 0
|
||||
|
||||
stacked_cases = []
|
||||
case_sequence = []
|
||||
case_index = 0
|
||||
current_case = None
|
||||
collision_x = TRAILER_X
|
||||
|
||||
def load_instruction_image():
|
||||
try:
|
||||
image = pygame.image.load("images/tutorial.png")
|
||||
return pygame.transform.scale(image, (SCREEN_WIDTH, SCREEN_HEIGHT))
|
||||
except:
|
||||
return None
|
||||
|
||||
instruction_image = load_instruction_image()
|
||||
|
||||
def show_instruction_screen():
|
||||
if not instruction_image:
|
||||
return
|
||||
waiting = True
|
||||
while waiting:
|
||||
screen.blit(instruction_image, (0, 0))
|
||||
pygame.display.flip()
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE) or \
|
||||
(event.type == pygame.JOYBUTTONDOWN and event.button == 0):
|
||||
waiting = False
|
||||
|
||||
|
||||
def generate_fill_sequence():
|
||||
global case_sequence
|
||||
try:
|
||||
with open("case_sequences.txt", "r") as f:
|
||||
sets = f.read().strip().split("\n\n")
|
||||
chosen = random.choice(sets).strip().split("\n")
|
||||
sequence = []
|
||||
for line in chosen:
|
||||
parts = line.strip().split(",")
|
||||
if len(parts) == 3:
|
||||
w, h, rot = int(parts[0]), int(parts[1]), parts[2].strip().lower() == 'true'
|
||||
sequence.append({'w': w, 'h': h, 'rotated': rot})
|
||||
case_sequence = sequence
|
||||
except:
|
||||
case_sequence = [{'w': 2, 'h': 1, 'rotated': False}, {'w': 1, 'h': 2, 'rotated': False}]
|
||||
|
||||
|
||||
def load_background():
|
||||
try:
|
||||
image = pygame.image.load("images/background.png")
|
||||
return pygame.transform.scale(image, (SCREEN_WIDTH, GAME_AREA_HEIGHT))
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
background_image = load_background()
|
||||
|
||||
case_images = {}
|
||||
|
||||
|
||||
def load_case_images():
|
||||
for w in range(1, 5):
|
||||
for h in range(1, 4):
|
||||
key = f"case_w{w}_h{h}"
|
||||
path = f"images/{key}.png"
|
||||
if os.path.exists(path):
|
||||
case_images[key] = pygame.image.load(path).convert_alpha()
|
||||
|
||||
|
||||
load_case_images()
|
||||
|
||||
|
||||
class Case:
|
||||
def animate_snap(self):
|
||||
pygame.mixer.Channel(1).stop()
|
||||
global collision_x
|
||||
# Snap-Animation: langsam einrasten an die Rückwand
|
||||
target_x = collision_x
|
||||
steps = 10
|
||||
delta = (self.x - target_x) / steps
|
||||
for _ in range(steps):
|
||||
self.x -= delta
|
||||
draw_game()
|
||||
pygame.display.flip()
|
||||
pygame.time.delay(int(1000 / transition_fps))
|
||||
self.x = target_x
|
||||
self.placed = True
|
||||
self.snapped = True
|
||||
collision_x = self.x + self.width
|
||||
|
||||
def mark_occupied(self):
|
||||
for base in reversed(stacked_cases):
|
||||
height_ok = base.y - self.height >= TRAILER_Y
|
||||
width_ok = self.width <= base.width
|
||||
space_free = all(not (
|
||||
other.y + other.height == base.y and
|
||||
other.x < base.x + base.width and
|
||||
other.x + other.width > base.x
|
||||
) for other in stacked_cases) and not getattr(base, 'occupied', False)
|
||||
if height_ok and width_ok and space_free:
|
||||
base.occupied = True
|
||||
break
|
||||
|
||||
def can_place_on_top(self):
|
||||
if self.placed:
|
||||
return False
|
||||
for base in reversed(stacked_cases):
|
||||
if base.y <= TRAILER_Y:
|
||||
continue
|
||||
height_ok = base.y - self.height >= TRAILER_Y
|
||||
width_ok = self.width <= base.width
|
||||
aligned_with_barrier = abs(base.x + base.width - collision_x) < 5
|
||||
space_free = all(not (
|
||||
other.y + other.height == base.y and
|
||||
other.x < base.x + base.width and
|
||||
other.x + other.width > base.x
|
||||
) for other in stacked_cases)
|
||||
if height_ok and width_ok and aligned_with_barrier and space_free:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __init__(self, size=None):
|
||||
self.rotated = False
|
||||
global case_index
|
||||
if not case_sequence:
|
||||
generate_fill_sequence()
|
||||
if size is None:
|
||||
size = case_sequence[case_index % len(case_sequence)]
|
||||
case_index += 1
|
||||
self.length_units, self.height_units = size['w'], size['h']
|
||||
if size['rotated']:
|
||||
self.length_units, self.height_units = self.height_units, self.length_units
|
||||
elif self.height_units * UNIT_LENGTH <= TRAILER_WIDTH and self.length_units * UNIT_HEIGHT <= TRAILER_HEIGHT and random.choice(
|
||||
[True, False]):
|
||||
self.length_units, self.height_units = self.height_units, self.length_units
|
||||
self.width = self.length_units * UNIT_LENGTH
|
||||
self.height = self.height_units * UNIT_HEIGHT
|
||||
self.x = SCREEN_WIDTH
|
||||
self.spawn_y = TRAILER_Y + TRAILER_HEIGHT - self.height
|
||||
self.y = self.spawn_y
|
||||
self.entry_angle = 0
|
||||
|
||||
self.color = random.choice(CASE_COLORS)
|
||||
self.placed = False
|
||||
self.allow_snap = False
|
||||
self.snapped = False
|
||||
self.occupied = False
|
||||
|
||||
def move(self):
|
||||
if not self.placed and not pygame.mixer.Channel(1).get_busy():
|
||||
pygame.mixer.Channel(1).play(sound_roll, loops=-1)
|
||||
return
|
||||
|
||||
distance_to_obstacle = self.x - collision_x
|
||||
speed = CASE_SPEED_FAST if distance_to_obstacle > FAST_TO_SLOW_DISTANCE else CASE_SPEED_SLOW
|
||||
self.x -= speed
|
||||
if self.x <= collision_x + SNAP_THRESHOLD:
|
||||
self.allow_snap = True
|
||||
|
||||
def collides_with_barrier(self):
|
||||
return not self.snapped and self.x <= collision_x
|
||||
|
||||
def draw(self, surface):
|
||||
angle = getattr(self, 'entry_angle', 0)
|
||||
if self.rotated:
|
||||
key = f"case_w{self.height_units}_h{self.length_units}"
|
||||
else:
|
||||
key = f"case_w{self.length_units}_h{self.height_units}"
|
||||
image = case_images.get(key)
|
||||
if image:
|
||||
if self.rotated:
|
||||
image_to_draw = pygame.transform.scale(image, (self.height, self.width))
|
||||
else:
|
||||
image_to_draw = pygame.transform.scale(image, (self.width, self.height))
|
||||
if self.rotated:
|
||||
image_to_draw = pygame.transform.rotate(image_to_draw, 90)
|
||||
if angle != 0:
|
||||
image_to_draw = pygame.transform.rotate(image_to_draw, angle)
|
||||
rect = image_to_draw.get_rect(center=(self.x + self.width // 2, self.y + self.height // 3))
|
||||
surface.blit(image_to_draw, rect)
|
||||
else:
|
||||
surface.blit(image_to_draw, (self.x, self.y))
|
||||
else:
|
||||
pygame.draw.rect(surface, self.color, (self.x, self.y, self.width, self.height))
|
||||
|
||||
def animate_tip(self):
|
||||
global current_case_visible
|
||||
current_case_visible = False
|
||||
key = f"case_w{self.length_units}_h{self.height_units}"
|
||||
image = case_images.get(key)
|
||||
if image:
|
||||
original = pygame.transform.scale(image, (self.width, self.height)).convert_alpha()
|
||||
else:
|
||||
original = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
|
||||
pygame.draw.rect(original, self.color, (0, 0, self.width, self.height))
|
||||
steps = 10
|
||||
for step in range(steps + 1):
|
||||
angle = step * (90 / steps)
|
||||
rotated = pygame.transform.rotate(original, angle)
|
||||
new_rect = rotated.get_rect(bottomleft=(self.x, self.y + self.height))
|
||||
draw_game()
|
||||
screen.blit(rotated, new_rect)
|
||||
pygame.display.flip()
|
||||
pygame.time.delay(int(1000 / transition_fps))
|
||||
self.width, self.height = self.height, self.width
|
||||
self.length_units, self.height_units = self.height_units, self.length_units
|
||||
self.rotated = not getattr(self, 'rotated', False)
|
||||
self.x = max(collision_x + SNAP_THRESHOLD // 2, self.x)
|
||||
self.y = TRAILER_Y + TRAILER_HEIGHT - self.height
|
||||
current_case_visible = True
|
||||
|
||||
def animate_place_on_top(self):
|
||||
pygame.mixer.Channel(1).stop()
|
||||
global current_case_visible, collision_x
|
||||
for base in reversed(stacked_cases):
|
||||
height_ok = base.y - self.height >= TRAILER_Y
|
||||
width_ok = self.width <= base.width
|
||||
space_free = all(not (
|
||||
other.y + other.height == base.y and
|
||||
other.x < base.x + base.width and
|
||||
other.x + other.width > base.x
|
||||
) for other in stacked_cases)
|
||||
if height_ok and width_ok and space_free:
|
||||
start_y = self.y
|
||||
target_y = base.y - self.height
|
||||
steps = 10
|
||||
for step in range(steps):
|
||||
self.y = start_y - ((start_y - target_y) * (step + 1) / steps)
|
||||
draw_game()
|
||||
pygame.display.flip()
|
||||
pygame.time.delay(int(1000 / transition_fps))
|
||||
self.y = target_y
|
||||
start_x = self.x
|
||||
target_x = base.x + base.width - self.width
|
||||
for step in range(steps):
|
||||
self.x = start_x - ((start_x - target_x) * (step + 1) / steps)
|
||||
draw_game()
|
||||
pygame.display.flip()
|
||||
pygame.time.delay(int(1000 / transition_fps))
|
||||
self.x = target_x
|
||||
self.placed = True
|
||||
self.snapped = True
|
||||
self.mark_occupied()
|
||||
collision_x = self.x + self.width
|
||||
return True
|
||||
current_case_visible = True
|
||||
return False
|
||||
|
||||
def snap_to_wall(self):
|
||||
global collision_x
|
||||
self.x = collision_x
|
||||
self.placed = True
|
||||
self.snapped = True
|
||||
collision_x = self.x + self.width
|
||||
|
||||
def fail_snap(self):
|
||||
pygame.mixer.Channel(1).stop()
|
||||
global collision_x
|
||||
self.x = collision_x + SNAP_THRESHOLD
|
||||
self.placed = True
|
||||
collision_x = self.x + self.width
|
||||
|
||||
|
||||
def draw_progress_bar(surface, percent):
|
||||
bar_width = SCREEN_WIDTH * 0.25
|
||||
bar_height = 30
|
||||
x = SCREEN_WIDTH - bar_width - 140
|
||||
y = TOP_BAR_HEIGHT // 2 - bar_height // 2
|
||||
gap = 8
|
||||
segment_width = (bar_width - 4 * gap) / 5
|
||||
red = min(255, int(percent * 2.55))
|
||||
green = max(0, 255 - int(percent * 2.55))
|
||||
color = (red, green, 0)
|
||||
for i in range(5):
|
||||
segment_x = x + i * (segment_width + gap)
|
||||
pygame.draw.rect(surface, (50, 50, 50), (segment_x, y, segment_width, bar_height), border_radius=10)
|
||||
if percent >= (i + 1) * 20:
|
||||
pygame.draw.rect(surface, color, (segment_x, y, segment_width, bar_height), border_radius=10)
|
||||
text = font.render(f"{int(percent)}%", True, (255, 255, 255))
|
||||
surface.blit(text, (x + bar_width + 10, y))
|
||||
|
||||
|
||||
def draw_title(surface):
|
||||
title = font.render("Habegger Trailer Load", True, (255, 255, 255))
|
||||
surface.blit(title, (40, TOP_BAR_HEIGHT // 2 - title.get_height() // 2))
|
||||
|
||||
|
||||
def calculate_load_percent():
|
||||
total_volume = TRAILER_WIDTH * TRAILER_HEIGHT
|
||||
used_volume = sum([c.width * c.height for c in stacked_cases])
|
||||
return min(100, (used_volume / total_volume) * 100)
|
||||
|
||||
|
||||
next_queue = []
|
||||
|
||||
|
||||
def draw_game(hide_trailer=False):
|
||||
global current_case
|
||||
global shake_timer
|
||||
global transition_counter
|
||||
|
||||
if transition_counter > 0:
|
||||
transition_counter -= 1
|
||||
pygame.time.delay(int(1000 / FPS))
|
||||
global current_case
|
||||
global shake_timer
|
||||
offset_x = random.randint(-5, 5) if shake_timer > 0 else 0
|
||||
offset_y = random.randint(-5, 5) if shake_timer > 0 else 0
|
||||
if shake_timer > 0:
|
||||
shake_timer -= 1
|
||||
screen.fill((0, 0, 0))
|
||||
pygame.draw.rect(screen, (0, 0, 0), (0, 0, SCREEN_WIDTH, TOP_BAR_HEIGHT))
|
||||
draw_title(screen)
|
||||
percent = calculate_load_percent()
|
||||
draw_progress_bar(screen, percent)
|
||||
if background_image:
|
||||
screen.blit(background_image, (0, TOP_BAR_HEIGHT))
|
||||
else:
|
||||
pygame.draw.rect(screen, (0, 0, 0), (0, TOP_BAR_HEIGHT, SCREEN_WIDTH, GAME_AREA_HEIGHT))
|
||||
if not hide_trailer:
|
||||
pygame.draw.rect(screen, (100, 100, 100),
|
||||
(TRAILER_X + offset_x, TRAILER_Y + offset_y, TRAILER_WIDTH, TRAILER_HEIGHT))
|
||||
for c in stacked_cases:
|
||||
c.draw(screen)
|
||||
if current_case and current_case_visible:
|
||||
current_case.draw(screen)
|
||||
pygame.draw.line(screen, (255, 0, 0), (collision_x, TRAILER_Y), (collision_x, TRAILER_Y + TRAILER_HEIGHT), 3)
|
||||
pygame.draw.rect(screen, (0, 0, 0), (0, TOP_BAR_HEIGHT + GAME_AREA_HEIGHT, SCREEN_WIDTH, BOTTOM_BAR_HEIGHT))
|
||||
if current_case and current_case.allow_snap:
|
||||
on_top_possible = current_case.can_place_on_top()
|
||||
if on_top_possible:
|
||||
snap_text = font.render("OnTop möglich!", True, (255, 255, 0))
|
||||
else:
|
||||
snap_text = font.render("Snap bereit!", True, (0, 255, 0))
|
||||
screen.blit(snap_text, (SCREEN_WIDTH - SCREEN_WIDTH // 3 - snap_text.get_width() // 2,
|
||||
SCREEN_HEIGHT - BOTTOM_BAR_HEIGHT // 2 - snap_text.get_height() // 2))
|
||||
|
||||
# Vorschau "Next"
|
||||
label = font.render("Next:", True, (255, 255, 255))
|
||||
screen.blit(label, (40, SCREEN_HEIGHT - BOTTOM_BAR_HEIGHT + 10))
|
||||
|
||||
preview_x = 40
|
||||
for i, case in enumerate(next_queue[:3]):
|
||||
w_units = case['w']
|
||||
h_units = case['h']
|
||||
if case['rotated']:
|
||||
w_units, h_units = h_units, w_units
|
||||
key = f"case_w{w_units}_h{h_units}"
|
||||
image = case_images.get(key)
|
||||
w = w_units * UNIT_LENGTH
|
||||
h = h_units * UNIT_HEIGHT
|
||||
if image:
|
||||
scaled = pygame.transform.scale(image, (w, h))
|
||||
screen.blit(scaled, (preview_x, SCREEN_HEIGHT - BOTTOM_BAR_HEIGHT + 40))
|
||||
else:
|
||||
pygame.draw.rect(screen, CASE_COLORS[i % len(CASE_COLORS)],
|
||||
(preview_x, SCREEN_HEIGHT - BOTTOM_BAR_HEIGHT + 40, w, h))
|
||||
preview_x += max(w, 80) + 20
|
||||
|
||||
|
||||
def spawn_case():
|
||||
global running
|
||||
global can_use_on_top
|
||||
can_use_on_top = True
|
||||
global case_sequence, case_index, next_queue
|
||||
if not case_sequence:
|
||||
generate_fill_sequence()
|
||||
|
||||
# Vorschau initial füllen
|
||||
while len(next_queue) < 3:
|
||||
index = (case_index + len(next_queue)) % len(case_sequence)
|
||||
next_queue.append(case_sequence[index])
|
||||
|
||||
# aktuelles Case bestimmen
|
||||
if case_index >= len(case_sequence):
|
||||
return None
|
||||
case_data = case_sequence[case_index % len(case_sequence)]
|
||||
case_index += 1
|
||||
|
||||
# Vorschau nachrücken
|
||||
if next_queue:
|
||||
next_queue.pop(0)
|
||||
while len(next_queue) < 3:
|
||||
index = (case_index + len(next_queue)) % len(case_sequence)
|
||||
next_queue.append(case_sequence[index])
|
||||
|
||||
return Case(size=case_data)
|
||||
|
||||
|
||||
def show_start_sequence():
|
||||
center_x = TRAILER_X + TRAILER_WIDTH // 2
|
||||
center_y = TRAILER_Y + TRAILER_HEIGHT // 2
|
||||
colors = [(255, 0, 0), (255, 200, 0), (0, 255, 0)]
|
||||
delays = [1000, 1000, 1000] # ms pro Punkt
|
||||
sounds = [sound_tut1, sound_tut2, sound_tuuut]
|
||||
radius = 90
|
||||
|
||||
for i in range(3):
|
||||
draw_game(hide_trailer=True)
|
||||
pygame.draw.circle(screen, colors[i], (center_x, center_y), radius)
|
||||
sounds[i].play()
|
||||
pygame.display.flip()
|
||||
pygame.time.delay(delays[i])
|
||||
|
||||
# Reißverschluss-Effekt
|
||||
zip_sound = pygame.mixer.Sound("zip.ogg")
|
||||
zip_sound.set_volume(1)
|
||||
zip_sound.play()
|
||||
duration = 200
|
||||
steps = 60
|
||||
for step in range(steps + 1):
|
||||
width = int(TRAILER_WIDTH * (step / steps))
|
||||
draw_game(hide_trailer=True)
|
||||
pygame.draw.rect(screen, (100, 100, 100), (TRAILER_X, TRAILER_Y, width, TRAILER_HEIGHT))
|
||||
pygame.display.flip()
|
||||
pygame.time.delay(duration // steps)
|
||||
|
||||
pygame.time.delay(1000)
|
||||
show_instruction_screen()
|
||||
show_start_sequence()
|
||||
current_case = spawn_case()
|
||||
|
||||
def show_game_over(score):
|
||||
try:
|
||||
with open("last_user.txt", "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
username = lines[1].strip() if len(lines) > 1 else "Spieler"
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["curl", "-X", "POST", "https://hag.putschgi.ch/Yb54CO5Ypybx/api/submit_points.php",
|
||||
"-d", f"name={username}",
|
||||
"-d", f"score={int(score)}",
|
||||
"-d", "api_key=hag-trailer-8051"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if result.returncode != 0 or not "success" in result.stdout.lower():
|
||||
raise Exception("Serverfehler")
|
||||
except:
|
||||
error_text = font.render("Fehler: Punkte konnten nicht gesendet werden!", True, (255, 100, 100))
|
||||
screen.blit(error_text, (SCREEN_WIDTH // 2 - error_text.get_width() // 2, SCREEN_HEIGHT // 2 + 60))
|
||||
pygame.display.flip()
|
||||
pygame.time.delay(2000)
|
||||
except:
|
||||
username = "Spieler"
|
||||
pygame.mixer.stop()
|
||||
if score >= 75:
|
||||
message = f"Gratuliere {username}! Du hast {int(score)}% geladen! Drücke A oder SPACE um zu beenden."
|
||||
else:
|
||||
message = f"{username}, du hast {int(score)}% geladen. Drücke A oder SPACE um zu beenden."
|
||||
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
|
||||
overlay.set_alpha(220)
|
||||
overlay.fill((0, 0, 0))
|
||||
screen.blit(overlay, (0, 0))
|
||||
text = font.render(message, True, (255, 255, 255))
|
||||
screen.blit(text, (SCREEN_WIDTH // 2 - text.get_width() // 2,
|
||||
SCREEN_HEIGHT // 2 - text.get_height() // 2))
|
||||
pygame.display.flip()
|
||||
waiting = True
|
||||
while waiting:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
elif (
|
||||
(event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE) or
|
||||
(event.type == pygame.JOYBUTTONDOWN and event.button == 0)
|
||||
):
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
running = True
|
||||
can_use_tilt = True
|
||||
can_use_on_top = True
|
||||
game_finished = False
|
||||
|
||||
|
||||
while running:
|
||||
if not game_finished and collision_x > TRAILER_X + TRAILER_WIDTH:
|
||||
show_game_over(calculate_load_percent())
|
||||
game_finished = True
|
||||
continue
|
||||
if not game_finished and current_case is None and case_index >= len(case_sequence):
|
||||
show_game_over(calculate_load_percent())
|
||||
game_finished = True
|
||||
continue
|
||||
clock.tick(FPS)
|
||||
if transition_counter > 0:
|
||||
transition_counter -= 1
|
||||
draw_game()
|
||||
pygame.display.flip()
|
||||
pygame.time.delay(int(1000 / FPS))
|
||||
continue
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
running = False
|
||||
elif event.type == pygame.KEYDOWN or event.type == pygame.JOYBUTTONDOWN or event.type == pygame.JOYHATMOTION:
|
||||
if state == PLAYING:
|
||||
can_tip = current_case.length_units * UNIT_HEIGHT <= TRAILER_HEIGHT
|
||||
if (
|
||||
(event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT) or
|
||||
(event.type == pygame.JOYHATMOTION and event.value[0] == -1) or
|
||||
(event.type == pygame.JOYBUTTONDOWN and event.button == 2)
|
||||
):
|
||||
if current_case and current_case.allow_snap and can_use_tilt and can_tip:
|
||||
current_case.animate_tip()
|
||||
can_use_tilt = False
|
||||
elif (
|
||||
(event.type == pygame.KEYDOWN and event.key == pygame.K_UP) or
|
||||
(event.type == pygame.JOYHATMOTION and event.value[1] == 1) or
|
||||
(event.type == pygame.JOYBUTTONDOWN and event.button == 3)
|
||||
):
|
||||
if current_case and current_case.allow_snap and can_use_on_top and current_case.can_place_on_top():
|
||||
if current_case.animate_place_on_top():
|
||||
transition_counter = transition_fps
|
||||
sound_place.play()
|
||||
stacked_cases.append(current_case)
|
||||
current_case = spawn_case()
|
||||
if current_case is None and not game_finished:
|
||||
show_game_over(calculate_load_percent())
|
||||
game_finished = True
|
||||
continue
|
||||
can_use_tilt = True
|
||||
can_use_on_top = True
|
||||
else:
|
||||
current_case.fail_snap()
|
||||
sound_place.play()
|
||||
stacked_cases.append(current_case)
|
||||
current_case = spawn_case()
|
||||
if current_case is None and not game_finished:
|
||||
show_game_over(calculate_load_percent())
|
||||
game_finished = True
|
||||
continue
|
||||
shake_timer = 10
|
||||
elif (
|
||||
(event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE) or
|
||||
(event.type == pygame.JOYBUTTONDOWN and event.button == 0)
|
||||
):
|
||||
if current_case and current_case.allow_snap:
|
||||
current_case.animate_snap()
|
||||
can_use_tilt = True
|
||||
can_use_on_top = True
|
||||
sound_place.play()
|
||||
transition_counter = transition_fps
|
||||
stacked_cases.append(current_case)
|
||||
current_case = spawn_case()
|
||||
|
||||
if state == PLAYING:
|
||||
if current_case is None:
|
||||
continue
|
||||
if current_case:
|
||||
current_case.move()
|
||||
if not game_finished:
|
||||
can_tip = current_case.length_units * UNIT_HEIGHT <= TRAILER_HEIGHT
|
||||
if collision_x + current_case.width > TRAILER_X + TRAILER_WIDTH and not current_case.can_place_on_top() and not current_case.allow_snap and not can_tip:
|
||||
show_game_over(calculate_load_percent())
|
||||
game_finished = True
|
||||
continue
|
||||
if calculate_load_percent() >= 99.9 and case_index >= len(case_sequence):
|
||||
show_game_over(100)
|
||||
game_finished = True
|
||||
continue
|
||||
if not current_case.placed and current_case.collides_with_barrier():
|
||||
current_case.fail_snap()
|
||||
if current_case.x + current_case.width > TRAILER_X + TRAILER_WIDTH:
|
||||
if stacked_cases and stacked_cases[-1] == current_case:
|
||||
stacked_cases.pop()
|
||||
else:
|
||||
stacked_cases.append(current_case)
|
||||
can_use_tilt = True
|
||||
can_use_on_top = True
|
||||
sound_fail.play()
|
||||
current_case = spawn_case()
|
||||
shake_timer = 10
|
||||
draw_game()
|
||||
|
||||
pygame.display.flip()
|
||||
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
805
main.py
@@ -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
@@ -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
@@ -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()
|
||||