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:
185
DEPLOYMENT.md
Normal file
185
DEPLOYMENT.md
Normal 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
26
build-frontend.sh
Normal 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
30
deploy-backend.sh
Normal 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
87
deploy.sh
Normal 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"
|
||||
@@ -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
56
rollback.sh
Normal 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
40
yourpart-http.conf
Normal 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
46
yourpart-https.conf
Normal 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
22
yourpart.service
Normal 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
|
||||
Reference in New Issue
Block a user