diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md
new file mode 100644
index 0000000..376799d
--- /dev/null
+++ b/DEPLOYMENT.md
@@ -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
diff --git a/build-frontend.sh b/build-frontend.sh
new file mode 100644
index 0000000..fc75a46
--- /dev/null
+++ b/build-frontend.sh
@@ -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!"
diff --git a/deploy-backend.sh b/deploy-backend.sh
new file mode 100644
index 0000000..96b17a7
--- /dev/null
+++ b/deploy-backend.sh
@@ -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!"
diff --git a/deploy.sh b/deploy.sh
new file mode 100644
index 0000000..0d241fb
--- /dev/null
+++ b/deploy.sh
@@ -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"
diff --git a/frontend/src/views/minigames/Match3Game.vue b/frontend/src/views/minigames/Match3Game.vue
index 992d972..afc9f13 100644
--- a/frontend/src/views/minigames/Match3Game.vue
+++ b/frontend/src/views/minigames/Match3Game.vue
@@ -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
diff --git a/rollback.sh b/rollback.sh
new file mode 100644
index 0000000..bcca7fb
--- /dev/null
+++ b/rollback.sh
@@ -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"
diff --git a/yourpart-http.conf b/yourpart-http.conf
new file mode 100644
index 0000000..95bf5cc
--- /dev/null
+++ b/yourpart-http.conf
@@ -0,0 +1,40 @@
+
+ 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
+
+ AllowOverride None
+ Options -Indexes +FollowSymLinks
+ Require all granted
+
+ # Fallback für Vue Router
+ FallbackResource /index.html
+
+
+ # 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]
+
diff --git a/yourpart-https.conf b/yourpart-https.conf
new file mode 100644
index 0000000..afb62d3
--- /dev/null
+++ b/yourpart-https.conf
@@ -0,0 +1,46 @@
+
+
+ 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
+
+ AllowOverride None
+ Options -Indexes +FollowSymLinks
+ Require all granted
+
+ # Fallback für Vue Router
+ FallbackResource /index.html
+
+
+ # 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]
+
+
diff --git a/yourpart.service b/yourpart.service
new file mode 100644
index 0000000..e3e0bb5
--- /dev/null
+++ b/yourpart.service
@@ -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