Add WebAssembly support for assets and chunks, implement gameover screen rendering, and enhance server gameplay logic with dynamic speeds, team naming, and score components.
@@ -1,89 +0,0 @@
|
||||
# Escape From Teacher - WASM Web Version
|
||||
|
||||
## 🎮 Starten
|
||||
|
||||
```bash
|
||||
# Im web-Verzeichnis einen HTTP-Server starten
|
||||
cd cmd/client/web
|
||||
python3 -m http.server 8000
|
||||
```
|
||||
|
||||
Dann im Browser öffnen: **http://localhost:8000**
|
||||
|
||||
## ✨ Features
|
||||
|
||||
### Modernes HTML/CSS Menü
|
||||
- **Custom Font**: Nutzt `front.ttf` aus dem assets-Ordner
|
||||
- **Responsive Design**: Funktioniert auf Desktop, Tablet und Smartphone
|
||||
- **Glassmorphism**: Moderner durchsichtiger Look mit Blur-Effekt
|
||||
- **Smooth Animations**: Fade-in, Slide-up, Pulse-Effekte
|
||||
|
||||
### Menü-Optionen
|
||||
1. **Solo spielen**: Einzelspieler-Modus
|
||||
2. **Co-op spielen**: Multiplayer mit Raum-Code
|
||||
3. **Leaderboard**: Top 10 Spieler anzeigen
|
||||
4. **Einstellungen**: Musik & SFX Lautstärke einstellen
|
||||
|
||||
### Mobile-Optimiert
|
||||
- Touch-freundliche Buttons
|
||||
- Responsive Layout für kleine Bildschirme
|
||||
- Viewport-Anpassung für Smartphones
|
||||
|
||||
## 🎨 Design-Features
|
||||
|
||||
- **Gradient Background**: Lila-Blauer Farbverlauf
|
||||
- **Button Hover-Effekte**: Ripple-Animationen
|
||||
- **Custom Scrollbars**: Für Leaderboard
|
||||
- **Loading Screen**: Spinner während WASM lädt
|
||||
- **Emoji-Icons**: 🏃 👥 🏆 ⚙️
|
||||
|
||||
## 📁 Dateien
|
||||
|
||||
- `index.html`: HTML-Struktur
|
||||
- `style.css`: Alle Styles mit Custom Font
|
||||
- `game.js`: JavaScript-Bridge zwischen HTML und WASM
|
||||
- `wasm_exec.js`: Go WASM Runtime (von Go kopiert)
|
||||
- `main.wasm`: Kompiliertes Spiel
|
||||
|
||||
## 🔧 Entwicklung
|
||||
|
||||
### WASM neu kompilieren
|
||||
```bash
|
||||
GOOS=js GOARCH=wasm go build -o cmd/client/web/main.wasm ./cmd/client
|
||||
```
|
||||
|
||||
### Font ändern
|
||||
Ersetze `cmd/client/assets/front.ttf` und aktualisiere den Pfad in `style.css`:
|
||||
```css
|
||||
@font-face {
|
||||
font-family: 'GameFont';
|
||||
src: url('../assets/front.ttf') format('truetype');
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 Keyboard Shortcuts
|
||||
|
||||
- **ESC**: Zurück zum Menü (während des Spiels)
|
||||
|
||||
## 📱 Mobile Testing
|
||||
|
||||
Für lokales Mobile Testing:
|
||||
```bash
|
||||
python3 -m http.server 8000
|
||||
# Öffne auf Smartphone: http://[DEINE-IP]:8000
|
||||
```
|
||||
|
||||
## 🚀 Production Deployment
|
||||
|
||||
Für Production alle Dateien hochladen:
|
||||
- index.html
|
||||
- style.css
|
||||
- game.js
|
||||
- wasm_exec.js
|
||||
- main.wasm
|
||||
- assets/ (Font-Ordner)
|
||||
|
||||
Server muss WASM mit korrektem MIME-Type ausliefern:
|
||||
```
|
||||
Content-Type: application/wasm
|
||||
```
|
||||
@@ -1 +0,0 @@
|
||||
../assets
|
||||
364
cmd/client/web/assets/assets.json
Normal file
@@ -0,0 +1,364 @@
|
||||
{
|
||||
"assets": {
|
||||
"baskeball": {
|
||||
"ID": "baskeball",
|
||||
"Type": "obstacle",
|
||||
"Filename": "baskeball.png",
|
||||
"Scale": 0.1,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": -3,
|
||||
"DrawOffY": -158,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 3,
|
||||
"OffsetY": 64,
|
||||
"W": 99,
|
||||
"H": 96,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"coin": {
|
||||
"ID": "coin",
|
||||
"Type": "coin",
|
||||
"Filename": "coin.png",
|
||||
"Scale": 0.1,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": -7,
|
||||
"DrawOffY": -163,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 6,
|
||||
"OffsetY": 63,
|
||||
"W": 94,
|
||||
"H": 100,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"desk": {
|
||||
"ID": "desk",
|
||||
"Type": "obstacle",
|
||||
"Filename": "desk.png",
|
||||
"Scale": 0.1,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": -2,
|
||||
"DrawOffY": -155,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 4,
|
||||
"OffsetY": 65,
|
||||
"W": 100,
|
||||
"H": 93,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"eraser": {
|
||||
"ID": "eraser",
|
||||
"Type": "obstacle",
|
||||
"Filename": "eraser.png",
|
||||
"Scale": 0.05,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": -29,
|
||||
"DrawOffY": -61,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 30,
|
||||
"OffsetY": 14,
|
||||
"W": 73,
|
||||
"H": 47,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"g-l": {
|
||||
"ID": "g-l",
|
||||
"Type": "obstacle",
|
||||
"Filename": "g-l.png",
|
||||
"Scale": 0.5,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": -65,
|
||||
"DrawOffY": -381,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 64,
|
||||
"OffsetY": 1,
|
||||
"W": 121,
|
||||
"H": 384,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"godmode": {
|
||||
"ID": "godmode",
|
||||
"Type": "powerup",
|
||||
"Filename": "godmode.png",
|
||||
"Scale": 0.08,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": 3,
|
||||
"DrawOffY": -90,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": -1,
|
||||
"OffsetY": 3,
|
||||
"W": 59,
|
||||
"H": 85,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"h-l": {
|
||||
"ID": "h-l",
|
||||
"Type": "obstacle",
|
||||
"Filename": "h-l.png",
|
||||
"Scale": 0.15,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": -19,
|
||||
"DrawOffY": -357,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 21,
|
||||
"OffsetY": 2,
|
||||
"W": 107,
|
||||
"H": 360,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"jump0": {
|
||||
"ID": "jump0",
|
||||
"Type": "obstacle",
|
||||
"Filename": "jump0.png",
|
||||
"Scale": 0.17,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": -8,
|
||||
"DrawOffY": -193,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 22,
|
||||
"OffsetY": 6,
|
||||
"W": 72,
|
||||
"H": 183,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"jump1": {
|
||||
"ID": "jump1",
|
||||
"Type": "obstacle",
|
||||
"Filename": "jump1.png",
|
||||
"Scale": 0.16,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": -1,
|
||||
"DrawOffY": -167,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 18,
|
||||
"OffsetY": 11,
|
||||
"W": 72,
|
||||
"H": 149,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"jumpboost": {
|
||||
"ID": "jumpboost",
|
||||
"Type": "powerup",
|
||||
"Filename": "jumpboost.png",
|
||||
"Scale": 0.09,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": 1,
|
||||
"DrawOffY": -81,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 3,
|
||||
"OffsetY": 2,
|
||||
"W": 97,
|
||||
"H": 76,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"k-l": {
|
||||
"ID": "k-l",
|
||||
"Type": "obstacle",
|
||||
"Filename": "k-l.png",
|
||||
"Scale": 0.12,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": -43,
|
||||
"DrawOffY": -228,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 43,
|
||||
"OffsetY": 5,
|
||||
"W": 78,
|
||||
"H": 222,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"k-l-monitor": {
|
||||
"ID": "k-l-monitor",
|
||||
"Type": "obstacle",
|
||||
"Filename": "k-l-monitor.png",
|
||||
"Scale": 0.15,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": -33,
|
||||
"DrawOffY": -332,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 7,
|
||||
"OffsetY": 10,
|
||||
"W": 147,
|
||||
"H": 328,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"k-m": {
|
||||
"ID": "k-m",
|
||||
"Type": "obstacle",
|
||||
"Filename": "k-m.png",
|
||||
"Scale": 1,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": -528,
|
||||
"DrawOffY": -2280,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": -528,
|
||||
"OffsetY": -2280,
|
||||
"W": 1056,
|
||||
"H": 2280,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"pc-trash": {
|
||||
"ID": "pc-trash",
|
||||
"Type": "obstacle",
|
||||
"Filename": "pc-trash.png",
|
||||
"Scale": 0.15,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": -3,
|
||||
"DrawOffY": -240,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 5,
|
||||
"OffsetY": 111,
|
||||
"W": 154,
|
||||
"H": 132,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"platform_1767135546": {
|
||||
"ID": "platform_1767135546",
|
||||
"Type": "platform",
|
||||
"Filename": "platform_1767135546.png",
|
||||
"Scale": 1,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": 1,
|
||||
"DrawOffY": -34,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 0,
|
||||
"OffsetY": 2,
|
||||
"W": 126,
|
||||
"H": 28,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"player": {
|
||||
"ID": "player",
|
||||
"Type": "obstacle",
|
||||
"Filename": "playernew.png",
|
||||
"Scale": 0.08,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": -56,
|
||||
"DrawOffY": -231,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 68,
|
||||
"OffsetY": 42,
|
||||
"W": 73,
|
||||
"H": 184,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"background": {
|
||||
"ID": "background",
|
||||
"Type": "background",
|
||||
"Filename": "background.jpg",
|
||||
"Scale": 1,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": 0,
|
||||
"DrawOffY": 0,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 0,
|
||||
"OffsetY": 0,
|
||||
"W": 0,
|
||||
"H": 0,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"background1": {
|
||||
"ID": "background1",
|
||||
"Type": "background",
|
||||
"Filename": "background1.jpg",
|
||||
"Scale": 1,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": 0,
|
||||
"DrawOffY": 0,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 0,
|
||||
"OffsetY": 0,
|
||||
"W": 0,
|
||||
"H": 0,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"background2": {
|
||||
"ID": "background2",
|
||||
"Type": "background",
|
||||
"Filename": "background2.jpg",
|
||||
"Scale": 1,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": 0,
|
||||
"DrawOffY": 0,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 0,
|
||||
"OffsetY": 0,
|
||||
"W": 0,
|
||||
"H": 0,
|
||||
"Type": ""
|
||||
}
|
||||
},
|
||||
"wall_1767369789": {
|
||||
"ID": "wall_1767369789",
|
||||
"Type": "obstacle",
|
||||
"Filename": "gen_wall_1767369789.png",
|
||||
"Scale": 1,
|
||||
"ProcWidth": 0,
|
||||
"ProcHeight": 0,
|
||||
"DrawOffX": 1,
|
||||
"DrawOffY": -127,
|
||||
"Color": {},
|
||||
"Hitbox": {
|
||||
"OffsetX": 4,
|
||||
"OffsetY": 12,
|
||||
"W": 55,
|
||||
"H": 113,
|
||||
"Type": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
cmd/client/web/assets/background.jpg
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
cmd/client/web/assets/background1.jpg
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
cmd/client/web/assets/background2.jpg
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
cmd/client/web/assets/baskeball.png
Normal file
|
After Width: | Height: | Size: 4.1 MiB |
41
cmd/client/web/assets/chunks/chunk_01.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"ID": "chunk_01",
|
||||
"Width": 50,
|
||||
"Objects": [
|
||||
{
|
||||
"AssetID": "platform_1767135546",
|
||||
"X": 419,
|
||||
"Y": 517
|
||||
},
|
||||
{
|
||||
"AssetID": "platform_1767135546",
|
||||
"X": 549,
|
||||
"Y": 516
|
||||
},
|
||||
{
|
||||
"AssetID": "platform_1767135546",
|
||||
"X": 682,
|
||||
"Y": 449
|
||||
},
|
||||
{
|
||||
"AssetID": "platform_1767135546",
|
||||
"X": 808,
|
||||
"Y": 449
|
||||
},
|
||||
{
|
||||
"AssetID": "eraser",
|
||||
"X": 1280,
|
||||
"Y": 529
|
||||
},
|
||||
{
|
||||
"AssetID": "pc-trash",
|
||||
"X": 1960,
|
||||
"Y": 533
|
||||
},
|
||||
{
|
||||
"AssetID": "coin",
|
||||
"X": 1024,
|
||||
"Y": 412
|
||||
}
|
||||
]
|
||||
}
|
||||
61
cmd/client/web/assets/chunks/chunk_02.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"ID": "chunk_02",
|
||||
"Width": 50,
|
||||
"Objects": [
|
||||
{
|
||||
"AssetID": "coin",
|
||||
"X": 512,
|
||||
"Y": 476
|
||||
},
|
||||
{
|
||||
"AssetID": "coin",
|
||||
"X": 704,
|
||||
"Y": 476
|
||||
},
|
||||
{
|
||||
"AssetID": "coin",
|
||||
"X": 1024,
|
||||
"Y": 476
|
||||
},
|
||||
{
|
||||
"AssetID": "platform_1767135546",
|
||||
"X": 1152,
|
||||
"Y": 484
|
||||
},
|
||||
{
|
||||
"AssetID": "platform_1767135546",
|
||||
"X": 1344,
|
||||
"Y": 420
|
||||
},
|
||||
{
|
||||
"AssetID": "platform_1767135546",
|
||||
"X": 1472,
|
||||
"Y": 420
|
||||
},
|
||||
{
|
||||
"AssetID": "platform_1767135546",
|
||||
"X": 1600,
|
||||
"Y": 420
|
||||
},
|
||||
{
|
||||
"AssetID": "platform_1767135546",
|
||||
"X": 1792,
|
||||
"Y": 292
|
||||
},
|
||||
{
|
||||
"AssetID": "platform_1767135546",
|
||||
"X": 1920,
|
||||
"Y": 292
|
||||
},
|
||||
{
|
||||
"AssetID": "platform_1767135546",
|
||||
"X": 2048,
|
||||
"Y": 292
|
||||
},
|
||||
{
|
||||
"AssetID": "coin",
|
||||
"X": 2112,
|
||||
"Y": 220
|
||||
}
|
||||
]
|
||||
}
|
||||
65
cmd/client/web/assets/chunks/chunk_03.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"ID": "chunk_03",
|
||||
"Width": 50,
|
||||
"Objects": [
|
||||
{
|
||||
"AssetID": "platform_1767135546",
|
||||
"X": 514,
|
||||
"Y": 519,
|
||||
"moving_platform": {
|
||||
"start_x": 514,
|
||||
"start_y": 522,
|
||||
"end_x": 800,
|
||||
"end_y": 239,
|
||||
"speed": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"AssetID": "coin",
|
||||
"X": 834,
|
||||
"Y": 204
|
||||
},
|
||||
{
|
||||
"AssetID": "wall_1767369789",
|
||||
"X": 1026,
|
||||
"Y": 539
|
||||
},
|
||||
{
|
||||
"AssetID": "wall_1767369789",
|
||||
"X": 1026,
|
||||
"Y": 412
|
||||
},
|
||||
{
|
||||
"AssetID": "platform_1767135546",
|
||||
"X": 1091,
|
||||
"Y": 318,
|
||||
"moving_platform": {
|
||||
"start_x": 1109,
|
||||
"start_y": 304,
|
||||
"end_x": 1898,
|
||||
"end_y": 307,
|
||||
"speed": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"AssetID": "desk",
|
||||
"X": 1421,
|
||||
"Y": 534
|
||||
},
|
||||
{
|
||||
"AssetID": "desk",
|
||||
"X": 1794,
|
||||
"Y": 535
|
||||
},
|
||||
{
|
||||
"AssetID": "coin",
|
||||
"X": 1169,
|
||||
"Y": 272
|
||||
},
|
||||
{
|
||||
"AssetID": "coin",
|
||||
"X": 1598,
|
||||
"Y": 260
|
||||
}
|
||||
]
|
||||
}
|
||||
16
cmd/client/web/assets/chunks/chunk_04.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"ID": "chunk_04",
|
||||
"Width": 50,
|
||||
"Objects": [
|
||||
{
|
||||
"AssetID": "godmode",
|
||||
"X": 569,
|
||||
"Y": 535
|
||||
},
|
||||
{
|
||||
"AssetID": "jumpboost",
|
||||
"X": 680,
|
||||
"Y": 538
|
||||
}
|
||||
]
|
||||
}
|
||||
5
cmd/client/web/assets/chunks/start.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"ID": "start",
|
||||
"Width": 20,
|
||||
"Objects": []
|
||||
}
|
||||
BIN
cmd/client/web/assets/coin.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
cmd/client/web/assets/desk.png
Normal file
|
After Width: | Height: | Size: 4.1 MiB |
BIN
cmd/client/web/assets/eraser.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
cmd/client/web/assets/front.ttf
Normal file
BIN
cmd/client/web/assets/g-l.png
Normal file
|
After Width: | Height: | Size: 366 KiB |
BIN
cmd/client/web/assets/game.mp3
Normal file
BIN
cmd/client/web/assets/game.wav
Normal file
BIN
cmd/client/web/assets/gen_plat_1767135546.png
Normal file
|
After Width: | Height: | Size: 222 B |
BIN
cmd/client/web/assets/gen_plat_1767369130.png
Normal file
|
After Width: | Height: | Size: 222 B |
BIN
cmd/client/web/assets/gen_wall_1767369789.png
Normal file
|
After Width: | Height: | Size: 307 B |
BIN
cmd/client/web/assets/godmode.png
Normal file
|
After Width: | Height: | Size: 653 KiB |
BIN
cmd/client/web/assets/h-l.png
Normal file
|
After Width: | Height: | Size: 3.0 MiB |
BIN
cmd/client/web/assets/jump.wav
Normal file
BIN
cmd/client/web/assets/jump0.png
Normal file
|
After Width: | Height: | Size: 559 KiB |
BIN
cmd/client/web/assets/jump1.png
Normal file
|
After Width: | Height: | Size: 545 KiB |
BIN
cmd/client/web/assets/jumpboost.png
Normal file
|
After Width: | Height: | Size: 696 KiB |
BIN
cmd/client/web/assets/k-l-monitor.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
cmd/client/web/assets/k-l.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
cmd/client/web/assets/k-m.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
cmd/client/web/assets/m-l.png
Normal file
|
After Width: | Height: | Size: 316 KiB |
BIN
cmd/client/web/assets/p-l.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 4.2 MiB After Width: | Height: | Size: 4.2 MiB |
BIN
cmd/client/web/assets/pickupCoin.wav
Normal file
BIN
cmd/client/web/assets/platform_1767135546.png
Normal file
|
After Width: | Height: | Size: 222 B |
BIN
cmd/client/web/assets/player.png
Normal file
|
After Width: | Height: | Size: 384 B |
BIN
cmd/client/web/assets/playernew.png
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
cmd/client/web/assets/powerUp.wav
Normal file
BIN
cmd/client/web/assets/r-l.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
cmd/client/web/assets/t-s.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
cmd/client/web/assets/w-l.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
cmd/client/web/background.jpg
Normal file
|
After Width: | Height: | Size: 142 KiB |
@@ -2,6 +2,130 @@
|
||||
let wasmReady = false;
|
||||
let gameStarted = false;
|
||||
let audioMuted = false;
|
||||
let currentLeaderboard = []; // Store full leaderboard data with proof codes
|
||||
|
||||
// UI State Management - Single Source of Truth
|
||||
const UIState = {
|
||||
LOADING: 'loading',
|
||||
MENU: 'menu',
|
||||
LOBBY: 'lobby',
|
||||
PLAYING: 'playing',
|
||||
GAME_OVER: 'gameover',
|
||||
LEADERBOARD: 'leaderboard',
|
||||
SETTINGS: 'settings',
|
||||
COOP_MENU: 'coop_menu',
|
||||
MY_CODES: 'mycodes',
|
||||
IMPRESSUM: 'impressum',
|
||||
DATENSCHUTZ: 'datenschutz'
|
||||
};
|
||||
|
||||
let currentUIState = UIState.LOADING;
|
||||
|
||||
// Central UI State Manager
|
||||
function setUIState(newState) {
|
||||
console.log('🎨 UI State:', currentUIState, '->', newState);
|
||||
currentUIState = newState;
|
||||
|
||||
const canvas = document.querySelector('canvas');
|
||||
const loadingScreen = document.getElementById('loading');
|
||||
|
||||
// Hide all overlays first
|
||||
document.querySelectorAll('.overlay-screen').forEach(screen => {
|
||||
screen.classList.add('hidden');
|
||||
});
|
||||
if (loadingScreen) loadingScreen.style.display = 'none';
|
||||
|
||||
// Manage Canvas and Overlays based on state
|
||||
switch(newState) {
|
||||
case UIState.LOADING:
|
||||
if (canvas) {
|
||||
canvas.classList.remove('game-active');
|
||||
canvas.style.visibility = 'hidden';
|
||||
}
|
||||
if (loadingScreen) loadingScreen.style.display = 'flex';
|
||||
break;
|
||||
|
||||
case UIState.MENU:
|
||||
if (canvas) {
|
||||
canvas.classList.remove('game-active');
|
||||
canvas.style.visibility = 'hidden';
|
||||
}
|
||||
document.getElementById('menu').classList.remove('hidden');
|
||||
break;
|
||||
|
||||
case UIState.LOBBY:
|
||||
if (canvas) {
|
||||
canvas.classList.remove('game-active');
|
||||
canvas.style.visibility = 'hidden';
|
||||
}
|
||||
document.getElementById('lobbyScreen').classList.remove('hidden');
|
||||
break;
|
||||
|
||||
case UIState.PLAYING:
|
||||
if (canvas) {
|
||||
canvas.classList.add('game-active');
|
||||
canvas.style.visibility = 'visible';
|
||||
}
|
||||
// No overlays shown during gameplay
|
||||
break;
|
||||
|
||||
case UIState.GAME_OVER:
|
||||
if (canvas) {
|
||||
canvas.classList.remove('game-active');
|
||||
canvas.style.visibility = 'hidden';
|
||||
}
|
||||
document.getElementById('gameOverScreen').classList.remove('hidden');
|
||||
break;
|
||||
|
||||
case UIState.LEADERBOARD:
|
||||
if (canvas) {
|
||||
canvas.classList.remove('game-active');
|
||||
canvas.style.visibility = 'hidden';
|
||||
}
|
||||
document.getElementById('leaderboardMenu').classList.remove('hidden');
|
||||
break;
|
||||
|
||||
case UIState.SETTINGS:
|
||||
if (canvas) {
|
||||
canvas.classList.remove('game-active');
|
||||
canvas.style.visibility = 'hidden';
|
||||
}
|
||||
document.getElementById('settingsMenu').classList.remove('hidden');
|
||||
break;
|
||||
|
||||
case UIState.COOP_MENU:
|
||||
if (canvas) {
|
||||
canvas.classList.remove('game-active');
|
||||
canvas.style.visibility = 'hidden';
|
||||
}
|
||||
document.getElementById('coopMenu').classList.remove('hidden');
|
||||
break;
|
||||
|
||||
case UIState.MY_CODES:
|
||||
if (canvas) {
|
||||
canvas.classList.remove('game-active');
|
||||
canvas.style.visibility = 'hidden';
|
||||
}
|
||||
document.getElementById('myCodesMenu').classList.remove('hidden');
|
||||
break;
|
||||
|
||||
case UIState.IMPRESSUM:
|
||||
if (canvas) {
|
||||
canvas.classList.remove('game-active');
|
||||
canvas.style.visibility = 'hidden';
|
||||
}
|
||||
document.getElementById('impressumMenu').classList.remove('hidden');
|
||||
break;
|
||||
|
||||
case UIState.DATENSCHUTZ:
|
||||
if (canvas) {
|
||||
canvas.classList.remove('game-active');
|
||||
canvas.style.visibility = 'hidden';
|
||||
}
|
||||
document.getElementById('datenschutzMenu').classList.remove('hidden');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// WebSocket for Leaderboard (direct JS connection)
|
||||
let leaderboardWS = null;
|
||||
@@ -89,8 +213,11 @@ async function initWASM() {
|
||||
go.run(result.instance);
|
||||
wasmReady = true;
|
||||
|
||||
// Hide loading screen
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
// Switch to menu state
|
||||
setUIState(UIState.MENU);
|
||||
|
||||
// Enable all start buttons
|
||||
enableStartButtons();
|
||||
|
||||
console.log('✅ WASM loaded successfully');
|
||||
|
||||
@@ -104,53 +231,32 @@ async function initWASM() {
|
||||
}
|
||||
}
|
||||
|
||||
// Menu Navigation
|
||||
function showMainMenu() {
|
||||
hideAllScreens();
|
||||
document.getElementById('menu').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function showCoopMenu() {
|
||||
hideAllScreens();
|
||||
document.getElementById('coopMenu').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function showSettings() {
|
||||
hideAllScreens();
|
||||
document.getElementById('settingsMenu').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function showLeaderboard() {
|
||||
hideAllScreens();
|
||||
document.getElementById('leaderboardMenu').classList.remove('hidden');
|
||||
loadLeaderboard();
|
||||
}
|
||||
|
||||
function hideAllScreens() {
|
||||
document.querySelectorAll('.overlay-screen').forEach(screen => {
|
||||
screen.classList.add('hidden');
|
||||
// Enable start buttons after WASM is ready
|
||||
function enableStartButtons() {
|
||||
const buttons = ['startBtn', 'coopBtn', 'createRoomBtn', 'joinRoomBtn'];
|
||||
buttons.forEach(btnId => {
|
||||
const btn = document.getElementById(btnId);
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
btn.style.opacity = '1';
|
||||
btn.style.cursor = 'pointer';
|
||||
}
|
||||
});
|
||||
console.log('✅ Start-Buttons aktiviert (Solo + Coop)');
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
document.getElementById('menu').style.display = 'none';
|
||||
// Canvas sichtbar machen für Gameplay
|
||||
const canvas = document.querySelector('canvas');
|
||||
if (canvas) {
|
||||
canvas.classList.add('game-active');
|
||||
}
|
||||
}
|
||||
|
||||
function showMenu() {
|
||||
document.getElementById('menu').style.display = 'flex';
|
||||
document.getElementById('menu').classList.remove('hidden');
|
||||
showMainMenu();
|
||||
// Canvas verstecken im Menü
|
||||
const canvas = document.querySelector('canvas');
|
||||
if (canvas) {
|
||||
canvas.classList.remove('game-active');
|
||||
}
|
||||
}
|
||||
// Menu Navigation
|
||||
// Legacy function wrappers - use setUIState instead
|
||||
function showMainMenu() { setUIState(UIState.MENU); }
|
||||
function showCoopMenu() { setUIState(UIState.COOP_MENU); }
|
||||
function showSettings() { setUIState(UIState.SETTINGS); }
|
||||
function showLeaderboard() { setUIState(UIState.LEADERBOARD); loadLeaderboard(); }
|
||||
function showMyCodes() { setUIState(UIState.MY_CODES); loadMyCodes(); }
|
||||
function showImpressum() { setUIState(UIState.IMPRESSUM); }
|
||||
function showDatenschutz() { setUIState(UIState.DATENSCHUTZ); }
|
||||
function hideAllScreens() { /* Handled by setUIState */ }
|
||||
function hideMenu() { /* Handled by setUIState */ }
|
||||
function showMenu() { setUIState(UIState.MENU); }
|
||||
|
||||
// Game Functions
|
||||
function startSoloGame() {
|
||||
@@ -166,23 +272,17 @@ function startSoloGame() {
|
||||
localStorage.setItem('escape_game_mode', 'solo');
|
||||
localStorage.setItem('escape_room_id', '');
|
||||
|
||||
// Hide ALL screens including main menu
|
||||
hideAllScreens();
|
||||
document.getElementById('menu').style.display = 'none';
|
||||
gameStarted = true;
|
||||
|
||||
// Canvas sichtbar machen
|
||||
const canvas = document.querySelector('canvas');
|
||||
if (canvas) {
|
||||
canvas.classList.add('game-active');
|
||||
}
|
||||
// Don't switch UI state yet - wait for WASM callback onGameStarted()
|
||||
// The server will auto-start solo games after 2 seconds
|
||||
|
||||
// Trigger WASM game start
|
||||
if (window.startGame) {
|
||||
window.startGame('solo', playerName, '');
|
||||
}
|
||||
|
||||
console.log('🎮 Solo game started:', playerName);
|
||||
console.log('🎮 Solo game starting - waiting for server auto-start...');
|
||||
}
|
||||
|
||||
function createRoom() {
|
||||
@@ -202,12 +302,8 @@ function createRoom() {
|
||||
localStorage.setItem('escape_team_name', teamName);
|
||||
localStorage.setItem('escape_is_host', 'true');
|
||||
|
||||
// Verstecke ALLE Screens inkl. Hauptmenü
|
||||
hideAllScreens();
|
||||
document.getElementById('menu').style.display = 'none';
|
||||
|
||||
// Zeige HTML Lobby Screen
|
||||
document.getElementById('lobbyScreen').classList.remove('hidden');
|
||||
// Show Lobby
|
||||
setUIState(UIState.LOBBY);
|
||||
document.getElementById('lobbyRoomCode').textContent = roomID;
|
||||
document.getElementById('lobbyHostControls').classList.remove('hidden');
|
||||
document.getElementById('lobbyStatus').textContent = 'Du bist Host - starte wenn bereit!';
|
||||
@@ -242,12 +338,8 @@ function joinRoom() {
|
||||
localStorage.setItem('escape_team_name', teamName);
|
||||
localStorage.setItem('escape_is_host', 'false');
|
||||
|
||||
// Verstecke ALLE Screens inkl. Hauptmenü
|
||||
hideAllScreens();
|
||||
document.getElementById('menu').style.display = 'none';
|
||||
|
||||
// Zeige HTML Lobby Screen
|
||||
document.getElementById('lobbyScreen').classList.remove('hidden');
|
||||
// Show Lobby
|
||||
setUIState(UIState.LOBBY);
|
||||
document.getElementById('lobbyRoomCode').textContent = roomID;
|
||||
document.getElementById('lobbyHostControls').classList.add('hidden');
|
||||
document.getElementById('lobbyStatus').textContent = 'Warte auf Host...';
|
||||
@@ -291,6 +383,42 @@ function updateLobbyPlayers(players) {
|
||||
console.log('👥 Lobby players updated:', players.length);
|
||||
}
|
||||
|
||||
// Update Lobby Team Name (called by WASM)
|
||||
function updateLobbyTeamName(teamName, isHost) {
|
||||
const teamNameBox = document.getElementById('lobbyTeamNameBox');
|
||||
const teamNameDisplay = document.getElementById('teamNameDisplay');
|
||||
|
||||
// Zeige Team-Name Box nur für Host
|
||||
if (isHost) {
|
||||
teamNameBox.classList.remove('hidden');
|
||||
|
||||
// Setup Event Listener für Input-Feld (nur einmal)
|
||||
const input = document.getElementById('lobbyTeamName');
|
||||
if (!input.dataset.listenerAdded) {
|
||||
input.addEventListener('input', function() {
|
||||
const newTeamName = this.value.toUpperCase().trim();
|
||||
if (newTeamName && window.setTeamName_WASM) {
|
||||
window.setTeamName_WASM(newTeamName);
|
||||
}
|
||||
});
|
||||
input.dataset.listenerAdded = 'true';
|
||||
}
|
||||
} else {
|
||||
teamNameBox.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Aktualisiere Team-Name Anzeige
|
||||
if (teamName && teamName !== '') {
|
||||
teamNameDisplay.textContent = teamName;
|
||||
teamNameDisplay.style.color = '#00ff00';
|
||||
} else {
|
||||
teamNameDisplay.textContent = 'Nicht gesetzt';
|
||||
teamNameDisplay.style.color = '#888';
|
||||
}
|
||||
|
||||
console.log('🏷️ Team name updated:', teamName, 'isHost:', isHost);
|
||||
}
|
||||
|
||||
function loadLeaderboard() {
|
||||
const list = document.getElementById('leaderboardList');
|
||||
list.innerHTML = '<div style="text-align:center; padding:20px;">Lädt Leaderboard...</div>';
|
||||
@@ -308,6 +436,9 @@ function loadLeaderboard() {
|
||||
|
||||
// Called by WASM to update leaderboard
|
||||
function updateLeaderboard(entries) {
|
||||
// Store full leaderboard data globally
|
||||
currentLeaderboard = entries || [];
|
||||
|
||||
// Update ALL leaderboard displays
|
||||
const list = document.getElementById('leaderboardList');
|
||||
const startList = document.getElementById('startLeaderboardList');
|
||||
@@ -423,8 +554,7 @@ document.addEventListener('keydown', (e) => {
|
||||
|
||||
// Show Game Over Screen (called by WASM)
|
||||
function showGameOver(score) {
|
||||
hideAllScreens();
|
||||
document.getElementById('gameOverScreen').classList.remove('hidden');
|
||||
setUIState(UIState.GAME_OVER);
|
||||
document.getElementById('finalScore').textContent = score;
|
||||
|
||||
// Update local highscore
|
||||
@@ -436,29 +566,137 @@ function showGameOver(score) {
|
||||
// Request leaderboard via direct WebSocket
|
||||
requestLeaderboardDirect();
|
||||
|
||||
console.log('💀 Game Over! Score:', score);
|
||||
// Note: Proof-Code wird jetzt direkt vom Server über score_response WebSocket Nachricht gesendet
|
||||
// und von WASM (connection_wasm.go) automatisch an saveHighscoreCode() weitergeleitet
|
||||
|
||||
console.log('💀 Game Over! Score:', score, '- Warte auf Proof-Code vom Server...');
|
||||
}
|
||||
|
||||
// Called by WASM when game actually starts
|
||||
function onGameStarted() {
|
||||
console.log('🎮 Game Started - Making canvas visible');
|
||||
hideAllScreens();
|
||||
document.getElementById('menu').style.display = 'none';
|
||||
gameStarted = true;
|
||||
setUIState(UIState.PLAYING);
|
||||
}
|
||||
|
||||
// Canvas sichtbar machen
|
||||
const canvas = document.querySelector('canvas');
|
||||
if (canvas) {
|
||||
canvas.classList.add('game-active');
|
||||
// ===== MY CODES MANAGEMENT =====
|
||||
|
||||
// Save highscore code to localStorage
|
||||
function saveHighscoreCode(score, proofCode, playerName) {
|
||||
const codes = getMySavedCodes();
|
||||
const newCode = {
|
||||
score: score,
|
||||
proof: proofCode,
|
||||
player_name: playerName,
|
||||
timestamp: Date.now(),
|
||||
date: new Date().toLocaleString('de-DE')
|
||||
};
|
||||
codes.push(newCode);
|
||||
localStorage.setItem('escape_highscore_codes', JSON.stringify(codes));
|
||||
console.log('💾 Highscore-Code gespeichert:', proofCode, 'für Score:', score);
|
||||
}
|
||||
|
||||
// Get all saved codes from localStorage
|
||||
function getMySavedCodes() {
|
||||
const stored = localStorage.getItem('escape_highscore_codes');
|
||||
if (!stored) return [];
|
||||
try {
|
||||
return JSON.parse(stored);
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Load and display my codes
|
||||
function loadMyCodes() {
|
||||
const codes = getMySavedCodes();
|
||||
const list = document.getElementById('myCodesList');
|
||||
|
||||
if (codes.length === 0) {
|
||||
list.innerHTML = '<div style="color: #888; text-align: center; padding: 20px;">Noch keine Highscores erreicht!</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort by score descending
|
||||
codes.sort((a, b) => b.score - a.score);
|
||||
|
||||
// Request current leaderboard to check positions
|
||||
requestLeaderboardForCodes(codes);
|
||||
}
|
||||
|
||||
// Request leaderboard and then display codes with positions
|
||||
function requestLeaderboardForCodes(codes) {
|
||||
const list = document.getElementById('myCodesList');
|
||||
list.innerHTML = '<div style="color: #888; text-align: center; padding: 20px;">Lade Positionen...</div>';
|
||||
|
||||
// Use the direct leaderboard WebSocket
|
||||
requestLeaderboardDirect();
|
||||
|
||||
// Wait a bit for leaderboard to arrive, then display
|
||||
setTimeout(() => {
|
||||
displayMyCodesWithPositions(codes);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Display codes with their leaderboard positions
|
||||
function displayMyCodesWithPositions(codes) {
|
||||
const list = document.getElementById('myCodesList');
|
||||
let html = '';
|
||||
|
||||
codes.forEach((code, index) => {
|
||||
// Try to find position in current leaderboard
|
||||
const position = findPositionInLeaderboard(code.proof);
|
||||
const positionText = position > 0 ? `#${position}` : 'Nicht in Top 10';
|
||||
const positionColor = position > 0 ? '#fc0' : '#888';
|
||||
|
||||
html += `
|
||||
<div style="background: rgba(0,0,0,0.4); border: 2px solid #fc0; padding: 12px; margin: 8px 0; border-radius: 4px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||
<div>
|
||||
<span style="color: #fc0; font-size: 20px; font-weight: bold;">${code.score} Punkte</span>
|
||||
<span style="color: ${positionColor}; font-size: 14px; margin-left: 10px;">${positionText}</span>
|
||||
</div>
|
||||
<button onclick="deleteHighscoreCode(${index})" style="background: #ff4444; border: none; color: white; padding: 5px 10px; font-size: 10px; cursor: pointer; border-radius: 3px;">LÖSCHEN</button>
|
||||
</div>
|
||||
<div style="font-family: sans-serif; font-size: 12px; color: #ccc;">
|
||||
<div><strong>Name:</strong> ${code.player_name}</div>
|
||||
<div><strong>Code:</strong> <span style="color: #fc0; font-family: monospace;">${code.proof}</span></div>
|
||||
<div><strong>Datum:</strong> ${code.date}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
list.innerHTML = html;
|
||||
}
|
||||
|
||||
// Find position of a proof code in the current leaderboard
|
||||
function findPositionInLeaderboard(proofCode) {
|
||||
if (!currentLeaderboard || currentLeaderboard.length === 0) return -1;
|
||||
|
||||
// Find the entry with matching proof code
|
||||
const index = currentLeaderboard.findIndex(entry => entry.proof_code === proofCode);
|
||||
return index >= 0 ? index + 1 : -1; // Return 1-based position
|
||||
}
|
||||
|
||||
// Delete a highscore code
|
||||
function deleteHighscoreCode(index) {
|
||||
if (!confirm('Diesen Highscore-Code wirklich löschen?')) return;
|
||||
|
||||
const codes = getMySavedCodes();
|
||||
codes.splice(index, 1);
|
||||
localStorage.setItem('escape_highscore_codes', JSON.stringify(codes));
|
||||
console.log('🗑️ Highscore-Code gelöscht');
|
||||
loadMyCodes(); // Reload display
|
||||
}
|
||||
|
||||
// Export functions for WASM to call
|
||||
window.showMenu = showMenu;
|
||||
window.hideMenu = hideMenu;
|
||||
window.updateLeaderboard = updateLeaderboard;
|
||||
window.showGameOver = showGameOver;
|
||||
window.onGameStarted = onGameStarted;
|
||||
window.saveHighscoreCode = saveHighscoreCode;
|
||||
|
||||
// Initialize on load
|
||||
initWASM();
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
|
||||
<input type="text" id="playerName" placeholder="NAME (4 ZEICHEN)" maxlength="15" style="text-transform:uppercase;">
|
||||
|
||||
<button id="startBtn" onclick="startSoloGame()">SOLO STARTEN</button>
|
||||
<button id="coopBtn" onclick="showCoopMenu()">CO-OP SPIELEN</button>
|
||||
<button id="startBtn" onclick="startSoloGame()" disabled style="opacity: 0.5; cursor: not-allowed;">SOLO STARTEN</button>
|
||||
<button id="coopBtn" onclick="showCoopMenu()" disabled style="opacity: 0.5; cursor: not-allowed;">CO-OP SPIELEN</button>
|
||||
|
||||
<div class="info-box">
|
||||
<div class="info-title">SCHUL-NEWS</div>
|
||||
@@ -49,8 +49,14 @@
|
||||
|
||||
<div class="legal-bar">
|
||||
<button class="legal-btn" onclick="showLeaderboard()">🏆 TOP 10</button>
|
||||
<button class="legal-btn" onclick="showMyCodes()">🔑 MEINE CODES</button>
|
||||
<button class="legal-btn" onclick="showSettings()">⚙️ EINSTELLUNGEN</button>
|
||||
</div>
|
||||
|
||||
<div class="legal-bar" style="margin-top: 10px;">
|
||||
<button class="legal-btn" onclick="showImpressum()">📄 IMPRESSUM</button>
|
||||
<button class="legal-btn" onclick="showDatenschutz()">🔒 DATENSCHUTZ</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="start-right">
|
||||
@@ -67,13 +73,13 @@
|
||||
<div class="center-box">
|
||||
<h1>CO-OP MODUS</h1>
|
||||
|
||||
<button class="big-btn" onclick="createRoom()">RAUM ERSTELLEN</button>
|
||||
<button id="createRoomBtn" class="big-btn" onclick="createRoom()" disabled style="opacity: 0.5; cursor: not-allowed;">RAUM ERSTELLEN</button>
|
||||
|
||||
<div style="margin: 20px 0;">- ODER -</div>
|
||||
|
||||
<input type="text" id="joinRoomCode" placeholder="RAUM-CODE" maxlength="6" style="text-transform:uppercase;">
|
||||
<input type="text" id="teamNameJoin" placeholder="TEAM-NAME" maxlength="15">
|
||||
<button onclick="joinRoom()">RAUM BEITRETEN</button>
|
||||
<button id="joinRoomBtn" onclick="joinRoom()" disabled style="opacity: 0.5; cursor: not-allowed;">RAUM BEITRETEN</button>
|
||||
|
||||
<button class="back-btn" onclick="showMainMenu()">← ZURÜCK</button>
|
||||
</div>
|
||||
@@ -115,6 +121,112 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MY CODES MENU -->
|
||||
<div id="myCodesMenu" class="overlay-screen hidden">
|
||||
<div class="center-box">
|
||||
<h1>🔑 MEINE HIGHSCORE-CODES</h1>
|
||||
|
||||
<div id="myCodesList" class="leaderboard-box" style="max-height: 500px;">
|
||||
Keine Codes gespeichert.
|
||||
</div>
|
||||
|
||||
<button class="back-btn" onclick="showMainMenu()">← ZURÜCK</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IMPRESSUM MENU -->
|
||||
<div id="impressumMenu" class="overlay-screen hidden">
|
||||
<div class="center-box" style="max-width: 800px;">
|
||||
<h1>📄 IMPRESSUM & CREDITS</h1>
|
||||
|
||||
<div class="leaderboard-box" style="max-height: 500px; overflow-y: auto; text-align: left; font-family: sans-serif; font-size: 13px; line-height: 1.6; background: #333; color: #fff;">
|
||||
<h3 style="color: #fc0; margin-top: 0;">Projektleitung & Code:</h3>
|
||||
<p>Sebastian Unterschütz<br>
|
||||
Göltzschtalblick 16<br>
|
||||
08236 Ellefeld<br>
|
||||
<strong>Kontakt:</strong> sebastian@unterschuetz.de</p>
|
||||
|
||||
<h3 style="color: #fc0; margin-top: 20px;">🎵 Musik & Sound Design:</h3>
|
||||
<p>Max E.</p>
|
||||
|
||||
<h3 style="color: #fc0; margin-top: 20px;">💻 Quellcode:</h3>
|
||||
<p><a href="https://git.zb-server.de/ZB-Server/it232Abschied" target="_blank" style="color: #fc0;">git.zb-server.de/ZB-Server/it232Abschied</a></p>
|
||||
|
||||
<h3 style="color: #fc0; margin-top: 20px;">⚖️ Lizenzhinweis:</h3>
|
||||
<p style="background: rgba(255,204,0,0.1); padding: 10px; border-left: 3px solid #fc0;">
|
||||
Dies ist ein <strong>Schulprojekt</strong>.<br>
|
||||
Kommerzielle Nutzung und Veränderung des Quellcodes sind ausdrücklich untersagt.<br>
|
||||
Alle Rechte liegen bei den Urhebern.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button class="back-btn" onclick="showMainMenu()">← ZURÜCK</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DATENSCHUTZ MENU -->
|
||||
<div id="datenschutzMenu" class="overlay-screen hidden">
|
||||
<div class="center-box" style="max-width: 800px;">
|
||||
<h1>🔒 DATENSCHUTZERKLÄRUNG</h1>
|
||||
|
||||
<div class="leaderboard-box" style="max-height: 500px; overflow-y: auto; text-align: left; font-family: sans-serif; font-size: 13px; line-height: 1.6; background: #333; color: #fff;">
|
||||
<h3 style="color: #fc0; margin-top: 0;">1. Datenschutz auf einen Blick</h3>
|
||||
<p><strong>Allgemeine Hinweise:</strong> Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen Daten passiert, wenn Sie diese Website besuchen.</p>
|
||||
|
||||
<h3 style="color: #fc0; margin-top: 20px;">2. Verantwortlicher</h3>
|
||||
<p>Verantwortlich für die Datenverarbeitung auf dieser Website ist:<br>
|
||||
<strong>Sebastian Unterschütz</strong><br>
|
||||
Göltzschtalblick 16, 08236 Ellefeld<br>
|
||||
E-Mail: sebastian@unterschuetz.de<br>
|
||||
(Schulprojekt im Rahmen der IT232)</p>
|
||||
|
||||
<h3 style="color: #fc0; margin-top: 20px;">3. Hosting (Hetzner)</h3>
|
||||
<p>Wir hosten die Inhalte unserer Website bei folgendem Anbieter:<br>
|
||||
<strong>Hetzner Online GmbH</strong><br>
|
||||
Industriestr. 25, 91710 Gunzenhausen, Deutschland</p>
|
||||
<p><strong>Serverstandort:</strong> Deutschland (ausschließlich).<br>
|
||||
Wir haben mit dem Anbieter einen Vertrag zur Auftragsverarbeitung (AVV) geschlossen, der die Einhaltung der DSGVO gewährleistet.</p>
|
||||
|
||||
<h3 style="color: #fc0; margin-top: 20px;">4. Datenerfassung auf dieser Website</h3>
|
||||
|
||||
<h4 style="color: #fc0; margin-top: 15px;">Server-Log-Dateien</h4>
|
||||
<p>Der Provider der Seiten (Hetzner) erhebt und speichert automatisch Informationen in so genannten Server-Log-Dateien (Browser, OS, Referrer, Hostname, Uhrzeit, IP-Adresse).<br>
|
||||
<strong>Rechtsgrundlage:</strong> Art. 6 Abs. 1 lit. f DSGVO (Berechtigtes Interesse an technischer Fehlerfreiheit und Sicherheit). Die Daten werden nach spätestens 14 Tagen gelöscht.</p>
|
||||
|
||||
<h4 style="color: #fc0; margin-top: 15px;">Spielstände & Highscores</h4>
|
||||
<p>Wenn Sie einen Highscore eintragen, speichern wir:</p>
|
||||
<ul style="margin-left: 20px;">
|
||||
<li>Gewählter Name (Pseudonym empfohlen!)</li>
|
||||
<li>Punktestand und Zeitstempel</li>
|
||||
<li>Eindeutiger Player-Code (generiert)</li>
|
||||
<li>Proof-Code (kryptografischer Hash zur Verifizierung des Scores)</li>
|
||||
</ul>
|
||||
<p>Diese Daten dienen der Darstellung der Bestenliste und der Verifikation Ihrer Highscores.</p>
|
||||
|
||||
<h4 style="color: #fc0; margin-top: 15px;">Lokale Speicherung (LocalStorage)</h4>
|
||||
<p>Das Spiel speichert folgende Daten lokal in Ihrem Browser:</p>
|
||||
<ul style="margin-left: 20px;">
|
||||
<li>Einstellungen (Audio-Lautstärke)</li>
|
||||
<li>Ihr Spielername</li>
|
||||
<li>Ihr Player-Code</li>
|
||||
<li>Ihre erreichten Highscore-Codes mit Proof-Codes</li>
|
||||
<li>Lokaler Highscore-Rekord</li>
|
||||
</ul>
|
||||
<p>Diese Daten verbleiben <strong>ausschließlich auf Ihrem Gerät</strong> und werden nicht an uns übertragen. Sie können diese Daten jederzeit über die Browser-Einstellungen löschen.</p>
|
||||
|
||||
<p style="background: rgba(255,204,0,0.1); padding: 10px; border-left: 3px solid #fc0; margin-top: 15px;">
|
||||
<strong>Wichtig:</strong> Wir setzen <strong>keine Tracking-Cookies</strong> oder Analyse-Tools ein. Es erfolgt keine Weitergabe Ihrer Daten an Dritte (außer technisch notwendig über Hetzner als Hosting-Provider).
|
||||
</p>
|
||||
|
||||
<h3 style="color: #fc0; margin-top: 20px;">5. Ihre Rechte</h3>
|
||||
<p>Sie haben jederzeit das Recht auf Auskunft, Berichtigung und Löschung Ihrer Daten. Wenden Sie sich dazu an den Verantwortlichen im Impressum.<br>
|
||||
Um Ihre lokal gespeicherten Highscore-Codes zu löschen, nutzen Sie die Funktion im Menü <strong>"🔑 MEINE CODES"</strong> oder löschen Sie den Browser-LocalStorage.</p>
|
||||
</div>
|
||||
|
||||
<button class="back-btn" onclick="showMainMenu()">← ZURÜCK</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LOBBY SCREEN (CO-OP WAITING ROOM) -->
|
||||
<div id="lobbyScreen" class="overlay-screen hidden">
|
||||
<div class="center-box">
|
||||
@@ -134,6 +246,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="lobbyTeamNameBox" class="hidden" style="margin: 20px 0; width: 100%; max-width: 400px;">
|
||||
<p style="font-size: 14px; color: #aaa; margin-bottom: 10px;">Team-Name (nur Host):</p>
|
||||
<input type="text" id="lobbyTeamName" placeholder="TEAM-NAME EINGEBEN" maxlength="15" style="text-transform:uppercase; width: 100%; padding: 10px; font-size: 16px;">
|
||||
<p id="currentTeamName" style="font-size: 12px; color: #ffcc00; margin-top: 5px;">Aktuell: <span id="teamNameDisplay">Nicht gesetzt</span></p>
|
||||
</div>
|
||||
|
||||
<div id="lobbyHostControls" class="hidden" style="margin: 20px 0;">
|
||||
<button class="big-btn" onclick="startGameFromLobby()" style="background: #00cc00;">SPIEL STARTEN</button>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@font-face{font-display:swap;font-family:'Press Start 2P';font-style:normal;font-weight:400;src:url('../assets/fonts/press-start-2p-v16-latin-regular.woff2') format('woff2')}
|
||||
body,html{margin:0;padding:0;width:100%;height:100%;background-color:#1a1a1a;color:#fff;overflow:hidden;font-family:'Press Start 2P',cursive;font-size:14px}
|
||||
#game-container{position:relative;width:100%;height:100%;box-shadow:0 0 50px rgba(0,0,0,.8);border:4px solid #444;background:#000}
|
||||
canvas{position:fixed!important;top:0!important;left:0!important;width:100%!important;height:100%!important;z-index:1!important;background:#000;image-rendering:pixelated;opacity:0;pointer-events:none;transition:opacity .3s}
|
||||
canvas.game-active{opacity:1;pointer-events:auto;z-index:2000!important}
|
||||
.overlay-screen{position:fixed!important;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.95);display:flex;justify-content:center;align-items:center;z-index:1000;box-sizing:border-box;padding:20px}
|
||||
canvas{position:fixed!important;top:0!important;left:0!important;width:100%!important;height:100%!important;z-index:1!important;background:#000;image-rendering:pixelated;opacity:0;pointer-events:none;transition:opacity .3s;visibility:hidden}
|
||||
canvas.game-active{opacity:1;pointer-events:auto;z-index:2000!important;visibility:visible}
|
||||
.overlay-screen{position:fixed!important;top:0;left:0;width:100%;height:100%;background:url('background.jpg') center/cover no-repeat,rgba(0,0,0,.85);display:flex;justify-content:center;align-items:center;z-index:1000;box-sizing:border-box;padding:20px}
|
||||
.overlay-screen.hidden{display:none!important}
|
||||
#startScreen{display:flex;flex-direction:row;gap:40px;width:100%;height:100%;align-items:center;justify-content:center}
|
||||
.start-left{flex:2;display:flex;flex-direction:column;align-items:center;justify-content:center;max-width:60%}
|
||||
@@ -14,15 +14,15 @@ button{font-family:'Press Start 2P',cursive;background:#fc0;border:4px solid #ff
|
||||
button:hover{background:#ffd700}
|
||||
button:active{transform:translateY(4px);box-shadow:0 1px 0 #997a00}
|
||||
.big-btn{font-size:22px;padding:20px 40px}
|
||||
.back-btn{background:0 0;border:2px solid #666;color:#888;box-shadow:none;font-size:12px;padding:10px 20px;margin-top:30px}
|
||||
.back-btn:hover{background:#333;color:#fff;border-color:#fff}
|
||||
.legal-btn{font-size:10px;padding:8px 12px;margin:5px;background:0 0;border:1px solid #666;color:#888;box-shadow:none}
|
||||
.legal-btn:hover{background:#333;color:#fff;border-color:#fff}
|
||||
.back-btn{background:#fc0;border:3px solid #fff;color:#000;box-shadow:0 4px 0 #997a00;font-size:12px;padding:10px 20px;margin-top:30px;font-weight:700}
|
||||
.back-btn:hover{background:#ffd700;color:#000;transform:translateY(2px);box-shadow:0 2px 0 #997a00}
|
||||
.legal-btn{font-size:10px;padding:8px 12px;margin:5px;background:rgba(0,0,0,.6);border:2px solid #fc0;color:#fc0;box-shadow:none}
|
||||
.legal-btn:hover{background:rgba(255,204,0,.2);color:#fff;border-color:#fff}
|
||||
input[type=text]{font-family:'Press Start 2P',cursive;padding:12px;font-size:16px;border:3px solid #fff;background:#222;color:#fff;text-align:center;margin-bottom:15px;width:100%;max-width:350px;outline:0;box-sizing:border-box}
|
||||
input[type=text]::placeholder{color:#666}
|
||||
input[type=text]:focus{border-color:#fc0;box-shadow:0 0 10px rgba(255,204,0,.5)}
|
||||
input[type=range]{width:100%;max-width:300px}
|
||||
.info-box{background:rgba(255,255,255,.1);border:2px solid #555;padding:12px;margin:8px 0;width:100%;max-width:320px;text-align:left;box-sizing:border-box}
|
||||
.info-box{background:rgba(0,0,0,.6);border:4px solid #fc0;padding:15px;margin:8px 0;width:100%;max-width:320px;text-align:left;box-sizing:border-box}
|
||||
.info-box p{font-family:sans-serif;font-size:14px;color:#ccc;line-height:1.4;margin:0}
|
||||
.info-title{color:#fc0;font-size:12px;margin-bottom:6px;text-align:center;text-decoration:underline}
|
||||
.legal-bar{margin-top:20px;display:flex;gap:15px;flex-wrap:wrap;justify-content:center}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { margin: 0; padding: 0; background: red; }
|
||||
.test-menu {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.9);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 48px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="test-menu">TEST - SIEHST DU MICH?</div>
|
||||
</body>
|
||||
</html>
|
||||