feat(match3): Verbesserung der Fall-Logik und Auffüllung leerer Felder

- Optimierung der Fall-Logik für Tiles, um sicherzustellen, dass leere Positionen korrekt gefüllt werden.
- Einführung einer neuen Methode zur Überprüfung und Auffüllung leerer gültiger Felder nach dem Fallen von Tiles.
- Anpassungen an der Animation und den Debug-Ausgaben zur besseren Nachverfolgbarkeit der Fall- und Auffüllprozesse.
- Verbesserung der Logik zur Handhabung von Tiles und deren Positionen im Spiel.
This commit is contained in:
Torsten Schulz (local)
2025-08-29 08:16:54 +02:00
parent 28833f0e0b
commit 5f99000f43
9 changed files with 655 additions and 137 deletions

185
DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,185 @@
# YourPart Deployment Anleitung
Diese Anleitung beschreibt, wie Sie die YourPart-Anwendung auf einem Ubuntu 22.04 Server deployen.
## Voraussetzungen
- Ubuntu 22.04 Server
- Node.js 18+ installiert
- MySQL/MariaDB installiert und konfiguriert
- Redis installiert
- Apache2 installiert
- SSL-Zertifikate von Let's Encrypt (bereits vorhanden)
## Installation der Abhängigkeiten
```bash
# Node.js installieren
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
# MySQL installieren
sudo apt install mysql-server
# Redis installieren
sudo apt install redis-server
# Apache2 installieren
sudo apt install apache2
# Apache-Module aktivieren
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_wstunnel
sudo a2enmod rewrite
sudo a2enmod ssl
```
## Konfiguration
### 1. Datenbank einrichten
```bash
sudo mysql_secure_installation
sudo mysql -u root -p
```
```sql
CREATE DATABASE yourpart CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'yourpart'@'localhost' IDENTIFIED BY 'your_secure_password';
GRANT ALL PRIVILEGES ON yourpart.* TO 'yourpart'@'localhost';
FLUSH PRIVILEGES;
EXIT;
```
### 2. Redis konfigurieren
```bash
sudo nano /etc/redis/redis.conf
```
Stellen Sie sicher, dass Redis auf localhost läuft und ein Passwort gesetzt ist.
### 3. Umgebungsvariablen konfigurieren
Erstellen Sie eine `.env`-Datei im Backend-Verzeichnis:
```bash
cd backend
cp .env.example .env
nano .env
```
Wichtige Variablen:
```env
NODE_ENV=production
PORT=2020
DB_HOST=localhost
DB_USER=yourpart
DB_PASS=your_secure_password
DB_NAME=yourpart
REDIS_HOST=localhost
REDIS_PASS=your_redis_password
```
## Deployment
### 1. Skripte ausführbar machen
```bash
chmod +x deploy.sh deploy-backend.sh build-frontend.sh
```
### 2. Deployment ausführen
```bash
./deploy.sh
```
Das Skript wird automatisch:
- Das Frontend bauen
- Den Backend-Service installieren
- Die Apache-Konfiguration einrichten
- Alle Services starten
## Verifizierung
### 1. Service-Status prüfen
```bash
sudo systemctl status yourpart.service
sudo systemctl status apache2
```
### 2. Logs überprüfen
```bash
# Backend-Logs
sudo journalctl -u yourpart.service -f
# Apache-Logs
sudo tail -f /var/log/apache2/yourpart.*.log
```
### 3. Anwendung testen
Öffnen Sie in Ihrem Browser:
- https://www.your-part.de
## Wartung
### Service neu starten
```bash
sudo systemctl restart yourpart.service
sudo systemctl reload apache2
```
### Updates deployen
```bash
git pull origin main
./deploy.sh
```
### Logs rotieren
```bash
sudo logrotate -f /etc/logrotate.d/apache2
```
## Troubleshooting
### Backend startet nicht
```bash
sudo journalctl -u yourpart.service -n 50
```
Prüfen Sie:
- Datenbankverbindung
- Redis-Verbindung
- Port 2020 ist verfügbar
- Berechtigungen in `/opt/yourpart/backend`
### Apache-Fehler
```bash
sudo apache2ctl configtest
sudo tail -f /var/log/apache2/error.log
```
### SSL-Probleme
```bash
sudo certbot certificates
sudo certbot renew --dry-run
```
## Sicherheit
- Firewall konfigurieren (nur Port 80, 443, 22 öffnen)
- Regelmäßige Updates
- Datenbank-Backups
- SSL-Zertifikate erneuern
- Logs überwachen

26
build-frontend.sh Normal file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
echo "Building YourPart Frontend..."
# Zum Frontend-Verzeichnis wechseln
cd frontend
# Dependencies installieren
echo "Installing dependencies..."
npm ci --production
# Frontend bauen
echo "Building frontend..."
npm run build
# Build-Verzeichnis nach /opt/yourpart kopieren
echo "Copying build to /opt/yourpart/frontend/dist..."
sudo mkdir -p /opt/yourpart/frontend
sudo cp -r dist /opt/yourpart/frontend/
# Berechtigungen setzen
echo "Setting permissions..."
sudo chown -R www-data:www-data /opt/yourpart/frontend
sudo chmod -R 755 /opt/yourpart/frontend
echo "Frontend build completed!"

30
deploy-backend.sh Normal file
View File

@@ -0,0 +1,30 @@
#!/bin/bash
echo "Deploying YourPart Backend..."
# Zum Backend-Verzeichnis wechseln
cd backend
# Dependencies installieren
echo "Installing dependencies..."
npm ci --production
# Backend nach /opt/yourpart kopieren
echo "Copying backend to /opt/yourpart/backend..."
sudo mkdir -p /opt/yourpart/backend
sudo cp -r * /opt/yourpart/backend/
# Berechtigungen setzen
echo "Setting permissions..."
sudo chown -R www-data:www-data /opt/yourpart/backend
sudo chmod -R 755 /opt/yourpart/backend
# .env-Datei kopieren (falls vorhanden)
if [ -f .env ]; then
echo "Copying .env file..."
sudo cp .env /opt/yourpart/backend/
sudo chown www-data:www-data /opt/yourpart/backend/.env
sudo chmod 600 /opt/yourpart/backend/.env
fi
echo "Backend deployment completed!"

87
deploy.sh Normal file
View File

@@ -0,0 +1,87 @@
#!/bin/bash
echo "=== YourPart Deployment Script ==="
echo ""
# Prüfen ob wir im richtigen Verzeichnis sind
if [ ! -f "package.json" ]; then
echo "Error: Please run this script from the YourPart3 root directory"
exit 1
fi
# Prüfen ob sudo verfügbar ist
if ! command -v sudo &> /dev/null; then
echo "Error: sudo is required but not installed"
exit 1
fi
# Verzeichnisstruktur erstellen
echo "Creating directory structure..."
sudo mkdir -p /opt/yourpart/{frontend,backend}
# Backend deployen
echo ""
echo "=== Deploying Backend ==="
./deploy-backend.sh
# Frontend bauen und deployen
echo ""
echo "=== Building and Deploying Frontend ==="
./build-frontend.sh
# Systemd Service installieren
echo ""
echo "=== Installing Systemd Service ==="
sudo cp yourpart.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable yourpart.service
# Apache-Konfiguration installieren
echo ""
echo "=== Installing Apache Configuration ==="
sudo cp yourpart-http.conf /etc/apache2/sites-available/yourpart-http.conf
sudo cp yourpart-https.conf /etc/apache2/sites-available/yourpart-https.conf
# Alte Konfiguration deaktivieren (falls vorhanden)
sudo a2dissite yourpart 2>/dev/null || true
# Neue Konfiguration aktivieren
sudo a2ensite yourpart-http
sudo a2ensite yourpart-https
# Apache-Module aktivieren
echo "Enabling required Apache modules..."
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_wstunnel
sudo a2enmod rewrite
sudo a2enmod ssl
# Apache neu laden
echo "Reloading Apache..."
sudo systemctl reload apache2
# Backend-Service starten
echo ""
echo "=== Starting Backend Service ==="
sudo systemctl start yourpart.service
# Status anzeigen
echo ""
echo "=== Deployment Status ==="
echo "Backend Service Status:"
sudo systemctl status yourpart.service --no-pager -l
echo ""
echo "Apache Status:"
sudo systemctl status apache2 --no-pager -l
echo ""
echo "=== Deployment Completed! ==="
echo "Your application should now be available at:"
echo " HTTP: http://your-part.de (redirects to HTTPS)"
echo " HTTPS: https://www.your-part.de"
echo ""
echo "To check logs:"
echo " Backend: sudo journalctl -u yourpart.service -f"
echo " Apache: sudo tail -f /var/log/apache2/yourpart.*.log"

View File

@@ -293,18 +293,18 @@ export default {
movesLeft: 15,
matchesMade: 0, // Neue: Zählt tatsächlich gemachte Matches
// Drag & drop
dragStartIndex: null,
dragStartX: null,
dragStartY: null,
// Drag & drop
dragStartIndex: null,
dragStartX: null,
dragStartY: null,
originalTilePosition: null,
isDragging: false,
isDragging: false,
draggedTileIndex: null, // Neuer: Index des aktuell gedraggten Tiles
adjacentTilesForHover: [], // Neue: Liste der benachbarten Tiles für Hover
currentlyAnimatingTile: null, // Neuer: Index des aktuell animierten Tiles
dragElement: null,
dragOffsetX: 0,
dragOffsetY: 0,
dragElement: null,
dragOffsetX: 0,
dragOffsetY: 0,
boundMouseMoveHandler: null,
boundTouchMoveHandler: null,
@@ -505,10 +505,10 @@ export default {
});
},
// Neue Hilfsmethode ohne Rekursion
loadLevelDataInternal(levelData) {
return apiClient.get(`/api/match3/levels/${levelData.id}`)
.then(response => {
// Neue Hilfsmethode ohne Rekursion
loadLevelDataInternal(levelData) {
return apiClient.get(`/api/match3/levels/${levelData.id}`)
.then(response => {
if (response.data && response.data.success && response.data.data) {
const freshLevelData = response.data.data;
@@ -652,14 +652,14 @@ export default {
})
.catch(error => {
// Fallback: Setze currentLevel basierend auf completedLevels
if (this.completedLevels > 0) {
this.currentLevel = this.completedLevels + 1;
} else {
this.currentLevel = 1;
}
if (this.completedLevels > 0) {
this.currentLevel = this.completedLevels + 1;
} else {
this.currentLevel = 1;
}
// Lade das Level basierend auf dem Fallback currentLevel
this.loadLevelData(this.currentLevel);
});
});
},
async initializeLevel() {
@@ -668,8 +668,8 @@ export default {
return;
}
// Safety check: ensure currentLevelData is loaded
if (!this.currentLevelData) {
// Safety check: ensure currentLevelData is loaded
if (!this.currentLevelData) {
// KEINE REKURSION: Lade Kampagnendaten nur einmal
this.loadCampaignData().then(async () => {
// Nach dem Laden der Kampagne, initialisiere das Level direkt
@@ -783,14 +783,14 @@ export default {
} else {
console.log('🔧 Keine initialen Matches gefunden, Level ist bereit');
}
// WICHTIG: Nach der Korrektur der initialen Matches das Brett erneut überprüfen
console.log('🔍 Überprüfe das Brett nach der Korrektur der initialen Matches...');
await this.checkBoardAfterSetup();
// WICHTIG: Setze das Spiel als aktiv, aber prüfe NICHT sofort die Level-Objekte
this.gameActive = true;
this.gameActive = true;
// KEINE Prüfung der Level-Objekte beim Start - diese werden nur nach Spielzügen geprüft
// WICHTIG: Reset Flag für Level-Initialisierung
@@ -1107,7 +1107,7 @@ export default {
const index = this.coordsToIndex(r, col);
if (index && board[index] && board[index].type === centerType && !this.isPowerUpTile(board[index].type)) {
verticalLength++;
} else {
} else {
break;
}
}
@@ -1118,7 +1118,7 @@ export default {
const index = this.coordsToIndex(row, c);
if (index && board[index] && board[index].type === centerType && !this.isPowerUpTile(board[index].type)) {
horizontalLength++;
} else {
} else {
break;
}
}
@@ -1408,10 +1408,10 @@ export default {
// Prüfe auf 3er-Match
if (row + 2 < this.boardHeight) {
const index1 = this.coordsToIndex(row, col);
const index2 = this.coordsToIndex(row + 1, col);
const index3 = this.coordsToIndex(row + 2, col);
const index1 = this.coordsToIndex(row, col);
const index2 = this.coordsToIndex(row + 1, col);
const index3 = this.coordsToIndex(row + 2, col);
// Prüfe ob alle drei Positionen bereits Teil eines höherwertigen Matches sind
if (!usedIndices.has(index1) && !usedIndices.has(index2) && !usedIndices.has(index3) &&
this.isValidMatch(index1, index2, index3, board)) {
@@ -1679,7 +1679,7 @@ export default {
document.removeEventListener('mousemove', this.boundMouseMoveHandler);
this.boundMouseMoveHandler = null;
}
if (this.boundTouchMoveHandler) {
if (this.boundTouchMoveHandler) {
document.removeEventListener('touchmove', this.boundTouchMoveHandler);
this.boundTouchMoveHandler = null;
}
@@ -1709,7 +1709,7 @@ export default {
if (this.isFalling) {
return;
}
// WICHTIG: Prüfe auf Regenbogen-Tile Tausch (vor dem Tausch)
// Prüfe beide Kombinationen der ursprünglichen Tiles
const rainbowSwapResult = await this.handleRainbowSwap(this.board[index1], this.board[index2]);
@@ -1758,11 +1758,11 @@ export default {
this.moves--;
this.movesLeft++;
// Wichtig: Auch ohne Matches Level-Objekte prüfen
// Wichtig: Auch ohne Matches Level-Objekte prüfen
// (falls der Spieler bereits alle Ziele erreicht hat)
// Nur prüfen wenn der Spieler bereits gespielt hat UND das Level nicht initialisiert wird
if (this.moves > 0 && !this.isInitializingLevel) {
this.checkLevelObjectives();
this.checkLevelObjectives();
}
}
},
@@ -1864,7 +1864,7 @@ export default {
// WICHTIG: Erhöhe den Match-Zähler für Level-Objekte
if (isPlayerMove && !this.isInitializingLevel) {
this.matchesMade += 1;
this.matchesMade += 1;
console.log(`🎯 Match-Zähler erhöht: ${this.matchesMade} Matches gemacht`);
}
@@ -1969,8 +1969,8 @@ export default {
console.log('🔧 Debug: Alle Power-ups nach dem Fall-Down:');
this.debugPowerUps();
// Fülle leere Positionen mit neuen Tiles auf
await this.fillEmptyPositions();
// Fülle leere gültige Felder mit neuen Tiles auf
await this.checkAndFillEmptyValidFields();
// Debug: Zeige alle Power-ups nach dem Füllen
console.log('🔧 Debug: Alle Power-ups nach dem Füllen:');
@@ -2003,7 +2003,7 @@ export default {
// WICHTIG: Prüfe Level-Objekte nach dem Verarbeiten der Matches
if (isPlayerMove && !this.isInitializingLevel) {
console.log('🎯 Prüfe Level-Objekte nach Match-Verarbeitung...');
this.checkLevelObjectives();
this.checkLevelObjectives();
}
}
@@ -2015,94 +2015,127 @@ export default {
let hasChanges = true;
let iteration = 0;
const maxIterations = 10; // Verhindert Endlosschleifen
// Wiederhole bis keine weiteren Änderungen mehr auftreten
while (hasChanges && iteration < this.boardHeight) {
hasChanges = false;
while (hasChanges && iteration < maxIterations) {
iteration++;
console.log(`🔧 Fall-Down Iteration ${iteration}...`);
console.log(`🔧 Fall-Down Iteration ${iteration}`);
hasChanges = false;
// Sammle alle Tiles, die fallen sollen
const tilesToFall = [];
// Gehe von unten nach oben durch jede Spalte
// 1. Von unten nach oben leere Felder finden und Tiles nach unten verschieben
for (let col = 0; col < this.boardWidth; col++) {
for (let row = this.boardHeight - 2; row >= 0; row--) { // Von vorletzter Zeile nach oben
const currentIndex = this.coordsToIndex(row, col);
const belowIndex = this.coordsToIndex(row + 1, col);
console.log(`🔧 Prüfe Spalte ${col}...`);
// Finde alle leeren Positionen in dieser Spalte von unten nach oben
for (let row = this.boardHeight - 1; row >= 0; row--) {
const index = this.coordsToIndex(row, col);
// Wenn aktuelles Tile existiert und das darunter leer ist
if (this.board[currentIndex] && !this.board[belowIndex] && !this.isPowerUpTile(this.board[currentIndex].type)) {
// Prüfe, ob das Zielfeld im board_layout gültig ist (nicht 'o')
const targetRow = row + 1;
const targetCol = col;
// Hole das board_layout für das aktuelle Level
// Wenn Position leer ist, prüfe ob sie im Layout gültig ist
if (!this.board[index]) {
if (this.currentLevelData && this.currentLevelData.boardLayout) {
const layout = this.currentLevelData.boardLayout;
const layoutRows = layout.split('\n');
// Prüfe, ob das Zielfeld im Layout gültig ist
if (targetRow < layoutRows.length && targetCol < layoutRows[targetRow].length) {
const targetChar = layoutRows[targetRow][targetCol];
// Nur fallen lassen, wenn das Zielfeld 'x' ist (gültig)
if (targetChar === 'x') {
tilesToFall.push({
fromIndex: currentIndex,
toIndex: belowIndex,
fromRow: row,
toRow: row + 1,
col: col
});
hasChanges = true;
} else {
console.log(`🔧 Tile kann nicht auf Position [${targetRow}, ${targetCol}] fallen - Layout zeigt '${targetChar}'`);
const layoutRows = this.currentLevelData.boardLayout.split('\n');
if (row < layoutRows.length && col < layoutRows[row].length) {
const targetChar = layoutRows[row][col];
if (targetChar !== 'x') {
console.log(`🔧 Position [${row}, ${col}] ist ungültig im Layout (${targetChar}) - überspringe`);
continue; // Überspringe ungültige Positionen
}
}
} else {
// Fallback: Wenn kein Layout vorhanden, normale Logik verwenden
tilesToFall.push({
fromIndex: currentIndex,
toIndex: belowIndex,
fromRow: row,
toRow: row + 1,
col: col
});
}
console.log(`🔧 Leere Position gefunden: [${row}, ${col}] -> Index ${index}`);
// Suche nach oben nach einem Tile
let tileFound = false;
for (let searchRow = row - 1; searchRow >= 0; searchRow--) {
const searchIndex = this.coordsToIndex(searchRow, col);
hasChanges = true;
if (this.board[searchIndex] && !this.isPowerUpTile(this.board[searchIndex].type)) {
console.log(`🔧 Tile ${this.board[searchIndex].type} gefunden an [${searchRow}, ${col}] -> verschiebe nach [${row}, ${col}]`);
// Verschiebe Tile nach unten
this.board[index] = this.board[searchIndex];
this.board[searchIndex] = null;
// Aktualisiere DOM
this.$forceUpdate();
// Warte kurz für Animation
await this.wait(500);
hasChanges = true;
tileFound = true;
break;
}
}
if (!tileFound) {
console.log(`🔧 Kein Tile über Position [${row}, ${col}] gefunden`);
}
}
}
}
// Führe Fall-Animation für alle Tiles aus
if (tilesToFall.length > 0) {
console.log(`🔧 ${tilesToFall.length} Tiles fallen in Iteration ${iteration}`);
// 2. Oberste Zeile nach fehlenden Feldern untersuchen und auffüllen
if (hasChanges) {
console.log('🔧 Fülle oberste Zeile auf...');
// Führe Fall-Animation aus, BEVOR die Tiles im Board verschoben werden
await this.animateTilesFalling(tilesToFall);
for (let col = 0; col < this.boardWidth; col++) {
const index = this.coordsToIndex(0, col);
// Wenn oberste Position leer ist, prüfe ob sie im Layout gültig ist
if (!this.board[index]) {
if (this.currentLevelData && this.currentLevelData.boardLayout) {
const layoutRows = this.currentLevelData.boardLayout.split('\n');
if (0 < layoutRows.length && col < layoutRows[0].length) {
const targetChar = layoutRows[0][col];
if (targetChar !== 'x') {
console.log(`🔧 Oberste Position [0, ${col}] ist ungültig im Layout (${targetChar}) - überspringe`);
continue;
}
}
}
// Erstelle neues Tile
const newTile = this.createRandomTile();
this.board[index] = newTile;
console.log(`🔧 Neues Tile ${newTile.type} an oberster Position [0, ${col}] hinzugefügt`);
hasChanges = true;
}
}
// Bewege Tiles im Board NACH der Animation
tilesToFall.forEach(fallData => {
this.board[fallData.toIndex] = this.board[fallData.fromIndex];
this.board[fallData.fromIndex] = null;
});
// Aktualisiere die Anzeige nach dem Fallen
// Aktualisiere DOM nach dem Auffüllen
this.$forceUpdate();
await this.wait(100);
}
// Kürzere Pause zwischen den Iterationen für bessere Performance
await this.wait(50);
console.log(`🔧 Iteration ${iteration} abgeschlossen - Änderungen: ${hasChanges}`);
}
console.log(`🔧 Fall-Down abgeschlossen nach ${iteration} Iterationen`);
if (iteration >= maxIterations) {
console.log('⚠️ Maximale Anzahl von Fall-Down Iterationen erreicht');
}
// Nach dem Fallen: Prüfe ob alle gültigen Felder ein Tile enthalten
await this.checkAndFillEmptyValidFields();
console.log('🔧 Fall-Down-Logik abgeschlossen');
},
// Hilfsfunktion: Zeige den aktuellen Board-Zustand in der Konsole
printBoardState() {
console.log('🔧 Board-Zustand:');
for (let row = 0; row < this.boardHeight; row++) {
let rowStr = '';
for (let col = 0; col < this.boardWidth; col++) {
const index = this.coordsToIndex(row, col);
const tile = this.board[index];
if (tile) {
rowStr += `[${tile.type}] `;
} else {
rowStr += '[ ] ';
}
}
console.log(` Zeile ${row}: ${rowStr}`);
}
},
// Fülle leere Positionen mit neuen Tiles auf
@@ -2458,34 +2491,28 @@ export default {
console.log(`🎬 Schrumpf-Animation abgeschlossen`);
},
// Animierte Fall-Animation für Tiles
// Animierte Fall-Animation für Tiles (einfache Animation nach dem Verschieben im Board)
async animateTilesFalling(tilesToFall) {
console.log(`🎬 Starte Fall-Animation für ${tilesToFall.length} Tiles...`);
// Sammle alle DOM-Elemente der fallenden Tiles (an ihren ursprünglichen Positionen)
// Sammle alle DOM-Elemente der fallenden Tiles (an ihren neuen Positionen)
const tileElements = tilesToFall.map(fallData => {
const element = document.querySelector(`[data-index="${fallData.fromIndex}"]`);
const element = document.querySelector(`[data-index="${fallData.toIndex}"]`);
if (element) {
// Berechne die Fall-Distanz (sollte immer 1 Zeile sein)
const fallDistance = fallData.toRow - fallData.fromRow;
if (fallDistance !== 1) {
console.warn(`⚠️ Unerwartete Fall-Distanz: ${fallDistance} Zeilen für Tile ${fallData.fromIndex}`);
}
// Berechne die Fall-Distanz
const fallDistance = fallData.fallDistance || (fallData.toRow - fallData.fromRow);
const tileHeight = 60; // Höhe eines Tiles in Pixeln
const fallPixels = fallDistance * tileHeight;
// Setze das Tile an seine ursprüngliche Position (oben)
element.style.transform = `translateY(0px)`;
element.style.transition = 'transform 0.3s ease-out';
// Setze das Tile an seine ursprüngliche Position (oben) mit transform
element.style.transform = `translateY(-${fallPixels}px)`;
element.style.transition = 'transform 0.4s ease-out';
// Füge CSS-Klasse für die Fall-Animation hinzu
element.classList.add('falling');
// Speichere die Fall-Pixel für später
element.dataset.fallPixels = fallPixels;
console.log(`🎬 Fall-Animation für Tile ${fallData.fromIndex}: ${fallDistance} Zeile(n) (${fallPixels}px)`);
console.log(`🎬 Fall-Animation für Tile ${fallData.toIndex}: ${fallDistance} Zeile(n) (${fallPixels}px)`);
}
return element;
}).filter(element => element !== null);
@@ -2500,17 +2527,16 @@ export default {
// Warte kurz, damit die transform-Eigenschaft gesetzt wird
await this.wait(50);
// Bewege alle Tiles nach unten (Fall-Animation)
// Bewege alle Tiles zu ihrer finalen Position (Fall-Animation)
tileElements.forEach(element => {
const fallPixels = parseInt(element.dataset.fallPixels);
element.style.transform = `translateY(${fallPixels}px)`;
element.style.transform = 'translateY(0px)';
});
// Spiele Fall-Sound ab
this.playSound('falling');
// Warte auf die Fall-Animation (0,3 Sekunden)
await this.wait(300);
// Warte auf die Fall-Animation (0,4 Sekunden)
await this.wait(400);
// Entferne die CSS-Klassen und transform-Eigenschaften
tileElements.forEach(element => {
@@ -2749,8 +2775,8 @@ export default {
// Führe Fall-Down-Logik aus
await this.fallTilesDown();
// Fülle leere Positionen mit neuen Tiles auf
await this.fillEmptyPositions();
// Fülle leere gültige Felder mit neuen Tiles auf
await this.checkAndFillEmptyValidFields();
console.log('🔧 Fall-Prozess abgeschlossen');
},
@@ -2916,8 +2942,8 @@ export default {
// Führe Fall-Down-Logik aus
await this.fallTilesDown();
// Fülle leere Positionen mit neuen Tiles auf
await this.fillEmptyPositions();
// Fülle leere gültige Felder mit neuen Tiles auf
await this.checkAndFillEmptyValidFields();
// Erhöhe den Zug-Zähler
this.countPowerUpMove();
@@ -2961,8 +2987,8 @@ export default {
// Führe Fall-Down-Logik aus
await this.fallTilesDown();
// Fülle leere Positionen mit neuen Tiles auf
await this.fillEmptyPositions();
// Fülle leere gültige Felder mit neuen Tiles auf
await this.checkAndFillEmptyValidFields();
// Erhöhe den Zug-Zähler
this.countPowerUpMove();
@@ -3026,8 +3052,8 @@ export default {
// Führe Fall-Down-Logik aus
await this.fallTilesDown();
// Fülle leere Positionen mit neuen Tiles auf
await this.fillEmptyPositions();
// Fülle leere gültige Felder mit neuen Tiles auf
await this.checkAndFillEmptyValidFields();
// Erhöhe den Zug-Zähler
this.countPowerUpMove();
@@ -3090,8 +3116,8 @@ export default {
// Führe Fall-Down-Logik aus
await this.fallTilesDown();
// Fülle leere Positionen mit neuen Tiles auf
await this.fillEmptyPositions();
// Fülle leere gültige Felder mit neuen Tiles auf
await this.checkAndFillEmptyValidFields();
// Erhöhe den Zug-Zähler
this.countPowerUpMove();
@@ -3643,7 +3669,7 @@ export default {
draggedElement.style.transition = 'all 0.3s ease-out';
// Warte kurz und setze dann alle Styles komplett zurück
setTimeout(() => {
setTimeout(() => {
draggedElement.style.transition = '';
}, 300);
}
@@ -4781,8 +4807,8 @@ export default {
// Führe Fall-Down-Logik aus
await this.fallTilesDown();
// Fülle leere Positionen mit neuen Tiles auf
await this.fillEmptyPositions();
// Fülle leere gültige Felder mit neuen Tiles auf
await this.checkAndFillEmptyValidFields();
// Erhöhe den Zug-Zähler
this.countPowerUpMove();
@@ -4918,8 +4944,8 @@ export default {
// Führe Fall-Down-Logik aus
await this.fallTilesDown();
// Fülle leere Positionen mit neuen Tiles auf
await this.fillEmptyPositions();
// Fülle leere gültige Felder mit neuen Tiles auf
await this.checkAndFillEmptyValidFields();
// WICHTIG: Zug-Zähler wird bereits in handleRainbowSwap erhöht, nicht hier nochmal!
// this.countPowerUpMove(); // ENTFERNT - wird bereits in handleRainbowSwap aufgerufen

56
rollback.sh Normal file
View File

@@ -0,0 +1,56 @@
#!/bin/bash
echo "=== YourPart Rollback Script ==="
echo ""
# Bestätigung anfordern
read -p "Are you sure you want to rollback? This will stop the new service and restore the old configuration. (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Rollback cancelled."
exit 1
fi
echo "Starting rollback process..."
# Neuen Service stoppen und deaktivieren
echo "Stopping new YourPart service..."
sudo systemctl stop yourpart.service
sudo systemctl disable yourpart.service
# Alte Apache-Konfiguration wiederherstellen
echo "Restoring old Apache configuration..."
# Neue Konfigurationen deaktivieren
sudo a2dissite yourpart-http
sudo a2dissite yourpart-https
# Alte Konfiguration wieder aktivieren (falls vorhanden)
if [ -f "/etc/apache2/sites-available/yourpart" ]; then
echo "Re-enabling old configuration..."
sudo a2ensite yourpart
fi
# Apache neu laden
echo "Reloading Apache..."
sudo systemctl reload apache2
# Neue Dateien entfernen
echo "Removing new files..."
sudo rm -rf /opt/yourpart/backend
sudo rm -rf /opt/yourpart/frontend
sudo rm -f /etc/systemd/system/yourpart.service
sudo rm -f /etc/apache2/sites-available/yourpart-http.conf
sudo rm -f /etc/apache2/sites-available/yourpart-https.conf
# Systemd neu laden
sudo systemctl daemon-reload
echo ""
echo "=== Rollback Completed ==="
echo "The old configuration has been restored."
echo ""
echo "To verify:"
echo " - Check if the old service is running on port 2030"
echo " - Check Apache status: sudo systemctl status apache2"
echo " - Check if the old site is accessible"

40
yourpart-http.conf Normal file
View File

@@ -0,0 +1,40 @@
<VirtualHost your-part.de:80>
ServerAdmin webmaster@your-part.de
ServerName your-part.de
ServerAlias www.your-part.de
DocumentRoot /opt/yourpart/frontend/dist
DirectoryIndex index.html
# Frontend statische Dateien
<Directory "/opt/yourpart/frontend/dist">
AllowOverride None
Options -Indexes +FollowSymLinks
Require all granted
# Fallback für Vue Router
FallbackResource /index.html
</Directory>
# API-Requests an Backend weiterleiten
ProxyPass "/api/" "http://localhost:2020/api/"
ProxyPassReverse "/api/" "http://localhost:2020/api/"
# WebSocket-Requests an Backend weiterleiten
ProxyPass "/socket.io/" "http://localhost:2020/socket.io/"
ProxyPassReverse "/socket.io/" "http://localhost:2020/socket.io/"
ErrorLog /var/log/apache2/yourpart.error.log
CustomLog /var/log/apache2/yourpart.access.log combined
HostnameLookups Off
UseCanonicalName Off
ServerSignature On
# SSL Redirect
RewriteEngine on
RewriteCond %{SERVER_NAME} =www.your-part.de [OR]
RewriteCond %{SERVER_NAME} =your-part.de
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

46
yourpart-https.conf Normal file
View File

@@ -0,0 +1,46 @@
<IfModule mod_ssl.c>
<VirtualHost your-part.de:443>
ServerAdmin webmaster@your-part.de
ServerName your-part.de
ServerAlias www.your-part.de
DocumentRoot /opt/yourpart/frontend/dist
DirectoryIndex index.html
# Frontend statische Dateien
<Directory "/opt/yourpart/frontend/dist">
AllowOverride None
Options -Indexes +FollowSymLinks
Require all granted
# Fallback für Vue Router
FallbackResource /index.html
</Directory>
# API-Requests an Backend weiterleiten
ProxyPass "/api/" "http://localhost:2020/api/"
ProxyPassReverse "/api/" "http://localhost:2020/api/"
# WebSocket-Requests an Backend weiterleiten
ProxyPass "/socket.io/" "http://localhost:2020/socket.io/"
ProxyPassReverse "/socket.io/" "http://localhost:2020/socket.io/"
ErrorLog /var/log/apache2/yourpart.error.log
CustomLog /var/log/apache2/yourpart.access.log combined
HostnameLookups Off
UseCanonicalName Off
ServerSignature On
# SSL-Konfiguration
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/www.your-part.de/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/www.your-part.de/privkey.pem
# www Redirect
RewriteEngine on
RewriteCond %{SERVER_NAME} =your-part.de
RewriteRule ^ https://www.%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
</IfModule>

22
yourpart.service Normal file
View File

@@ -0,0 +1,22 @@
[Unit]
Description=YourPart Backend Server
After=network.target mysql.service redis-server.service
Wants=mysql.service redis-server.service
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/yourpart/backend
Environment=NODE_ENV=production
Environment=PORT=2020
ExecStart=/usr/bin/node server.js
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=yourpart-backend
[Install]
WantedBy=multi-user.target