From 39401840f27ac01303aa6cf0654b85731ab68191 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Mon, 25 Aug 2025 22:16:45 +0200 Subject: [PATCH] =?UTF-8?q?feat(match3):=20Erweiterung=20der=20Power-Up-Me?= =?UTF-8?q?chanik=20und=20Verbesserung=20der=20Benutzeroberfl=C3=A4che?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Hinzufügen von Raketen-Power-ups für 4er-Matches, einschließlich Logik zur Erstellung und Anzeige dieser Power-ups. - Anpassungen an der Darstellung von Tiles, um Raketen visuell hervorzuheben. - Einführung neuer Soundeffekte für das Fallen von Tiles und die Erstellung von Power-ups. - Verbesserung der Logik zur Handhabung von Matches, um 3er- und 4er-Matches zu berücksichtigen. - Implementierung einer Methode zur Überprüfung und Auffüllung leerer gültiger Felder nach dem Fallen von Tiles. --- frontend/src/views/minigames/Match3Game.vue | 216 +++++++++++++++++--- 1 file changed, 182 insertions(+), 34 deletions(-) diff --git a/frontend/src/views/minigames/Match3Game.vue b/frontend/src/views/minigames/Match3Game.vue index 104f570..22a1324 100644 --- a/frontend/src/views/minigames/Match3Game.vue +++ b/frontend/src/views/minigames/Match3Game.vue @@ -88,8 +88,15 @@ }"> @@ -354,25 +364,29 @@ export default { this.sounds.bomb = new Audio('/sounds/match3/bomb.wav'); this.sounds.rocket = new Audio('/sounds/match3/roket.wav'); // Beachte: Datei heißt "roket.wav" this.sounds.rainbow = new Audio('/sounds/match3/rainbow.wav'); + this.sounds.falling = new Audio('/sounds/match3/falling.wav'); // Setze Lautstärke auf 50% this.sounds.move.volume = 0.5; this.sounds.bomb.volume = 0.5; this.sounds.rocket.volume = 0.5; this.sounds.rainbow.volume = 0.5; + this.sounds.falling.volume = 0.5; // Warte bis alle Sounds geladen sind Promise.all([ this.sounds.move.load(), this.sounds.bomb.load(), this.sounds.rocket.load(), - this.sounds.rainbow.load() + this.sounds.rainbow.load(), + this.sounds.falling.load() ]).then(() => { console.log('🔊 Sounds erfolgreich geladen:', { move: this.sounds.move.readyState, bomb: this.sounds.bomb.readyState, rocket: this.sounds.rocket.readyState, - rainbow: this.sounds.rainbow.readyState + rainbow: this.sounds.rainbow.readyState, + falling: this.sounds.falling.readyState }); }).catch(error => { console.warn('⚠️ Fehler beim Laden der Sounds:', error); @@ -1136,6 +1150,11 @@ export default { return matches; }, + // Hilfsmethode: Prüfe ob ein Tile ein Raketen-Power-up ist + isRocketTile(tileType) { + return tileType === 'rocket-horizontal' || tileType === 'rocket-vertical'; + }, + // Hilfsmethode: Prüfe ob 3 Tiles einen gültigen Match bilden isValidMatch(index1, index2, index3, board) { // Prüfe ob alle Indizes gültig sind @@ -1352,25 +1371,33 @@ export default { console.log(`🔧 Verarbeite ${matches.length} Matches...`); - // WICHTIG: Filtere zuerst nach 3er-Matches und entferne Duplikate - const validMatches = matches.filter(match => match.length === 3); + // Erstelle Power-ups für 4er-Matches (BEVOR die Tiles entfernt werden) + await this.createPowerUpsForMatches(matches); - if (validMatches.length === 0) { - console.log('🔧 Keine 3er-Matches zum Verarbeiten'); + // Sammle alle zu entfernenden Tile-Indizes (aus 3er-Matches UND 4er-Matches) + const tilesToRemove = new Set(); + + matches.forEach((match, index) => { + if (match.length >= 3) { + console.log(`🔧 ${match.length}er-Match ${index + 1}: [${match.join(', ')}]`); + match.forEach(tileIndex => { + // Raketen-Power-ups nicht entfernen + if (this.board[tileIndex] && this.board[tileIndex].type && + (this.board[tileIndex].type === 'rocket-horizontal' || this.board[tileIndex].type === 'rocket-vertical')) { + console.log(`🔧 Rakete ${this.board[tileIndex].type} an Position ${tileIndex} wird NICHT entfernt`); + return; // Überspringe dieses Tile + } + tilesToRemove.add(tileIndex); + }); + } + }); + + if (tilesToRemove.size === 0) { + console.log('🔧 Keine Tiles zum Entfernen gefunden'); return; } - console.log(`🔧 ${validMatches.length} gültige 3er-Matches gefunden`); - - // Sammle alle zu entfernenden Tile-Indizes (nur aus 3er-Matches) - const tilesToRemove = new Set(); - - validMatches.forEach((match, index) => { - console.log(`🔧 3er-Match ${index + 1}: [${match.join(', ')}]`); - match.forEach(tileIndex => { - tilesToRemove.add(tileIndex); - }); - }); + console.log(`🔧 ${tilesToRemove.size} Tiles zum Entfernen gefunden`); // Starte Schrumpf-Animation für alle zu entfernenden Tiles console.log(`🔧 Starte Schrumpf-Animation für ${tilesToRemove.size} Tiles...`); @@ -1487,6 +1514,9 @@ export default { } console.log(`🔧 Fall-Down abgeschlossen nach ${iteration} Iterationen`); + + // Nach dem Fallen: Prüfe ob alle gültigen Felder ein Tile enthalten + await this.checkAndFillEmptyValidFields(); }, // Fülle leere Positionen mit neuen Tiles auf @@ -1556,6 +1586,105 @@ export default { console.log(`🔧 ${newTilesAdded} neue Tiles hinzugefügt`); }, + // Erstelle Power-ups für 4er-Matches + async createPowerUpsForMatches(matches) { + console.log('🔧 Prüfe auf Power-up-Erstellung...'); + + matches.forEach(match => { + if (match.length === 4) { + console.log(`🔧 4er-Match gefunden: ${match.join(', ')} - erstelle Rakete`); + + // Rakete erscheint an der Position des zweiten Tiles + const rocketIndex = match[1]; + const rocketTile = this.board[rocketIndex]; + + if (rocketTile) { + // Erstelle Rakete basierend auf der Richtung des Matches + const rocketType = this.determineRocketType(match); + this.board[rocketIndex] = { type: rocketType }; + + console.log(`🚀 Rakete ${rocketType} an Position ${rocketIndex} erstellt`); + } + } + }); + }, + + // Bestimme den Raketen-Typ basierend auf der Match-Richtung + determineRocketType(match) { + // Prüfe ob der Match horizontal oder vertikal ist + const firstPos = this.indexToCoords(match[0]); + const secondPos = this.indexToCoords(match[1]); + + if (firstPos.row === secondPos.row) { + // Horizontaler Match - horizontale Rakete + return 'rocket-horizontal'; + } else { + // Vertikaler Match - vertikale Rakete + return 'rocket-vertical'; + } + }, + + // Prüfe und fülle alle leeren gültigen Felder nach dem Fallen + async checkAndFillEmptyValidFields() { + console.log('🔧 Prüfe alle leeren gültigen Felder...'); + + let hasEmptyValidFields = false; + const emptyValidFields = []; + + // Gehe durch alle Positionen im Board + for (let row = 0; row < this.boardHeight; row++) { + for (let col = 0; col < this.boardWidth; col++) { + const index = this.coordsToIndex(row, col); + + // 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 Feld im Layout gültig ist (nicht 'o') + if (row < layoutRows.length && col < layoutRows[row].length) { + const targetChar = layoutRows[row][col]; + + if (targetChar === 'x') { + // Gültiges Feld ist leer - muss gefüllt werden + emptyValidFields.push({ index, row, col }); + hasEmptyValidFields = true; + console.log(`🔧 Gültiges Feld [${row}, ${col}] ist leer und muss gefüllt werden`); + } + } + } + } + } + } + + if (hasEmptyValidFields) { + console.log(`🔧 ${emptyValidFields.length} leere gültige Felder gefunden - starte erneuten Fall-Prozess`); + + // Fülle alle leeren gültigen Felder mit neuen Tiles + for (const field of emptyValidFields) { + const newTile = this.createRandomTile(); + this.board[field.index] = newTile; + console.log(`🔧 Neues Tile ${newTile.type} an Position [${field.row}, ${field.col}] hinzugefügt`); + } + + // Aktualisiere die Anzeige + this.$forceUpdate(); + + // Führe Animation für neue Tiles aus + await this.animateNewTilesAppearing(emptyValidFields.map(field => ({ + index: field.index, + row: field.row, + col: field.col, + type: this.board[field.index].type + }))); + + console.log(`🔧 Alle leeren gültigen Felder gefüllt`); + } else { + console.log('🔧 Alle gültigen Felder enthalten Tiles - Board ist vollständig'); + } + }, + // Animierte Entfernung von Tiles mit Schrumpf-Effekt async animateTileRemoval(tileIndices) { console.log(`🎬 Starte Schrumpf-Animation für ${tileIndices.length} Tiles...`); @@ -1637,6 +1766,9 @@ export default { element.style.transform = `translateY(${fallPixels}px)`; }); + // Spiele Fall-Sound ab + this.playSound('falling'); + // Warte auf die Fall-Animation (0,5 Sekunden) await this.wait(500); @@ -1932,22 +2064,23 @@ export default { // Warte auf Animation (0,75 Sekunden) await this.wait(750); - // Prüfe ob der Move einen 3er-Match erzeugt hat - const matches = this.findMatchesOnBoard(this.board, false); - const hasValidMatch = matches.some(match => match.length === 3); + // Prüfe ob der Move einen 3er-Match oder 4er-Match erzeugt hat + const matches = this.findMatchesOnBoard(this.board, false); + const hasValidMatch = matches.some(match => match.length >= 3); + + if (hasValidMatch) { + console.log(`✅ Move erfolgreich - ${matches.length} Match(es) gefunden!`); - if (hasValidMatch) { - console.log('✅ Move erfolgreich - 3er-Match gefunden!'); - await this.handleMatches(matches, true); - return true; - } else { - console.log('❌ Move nicht erfolgreich - Kein 3er-Match'); - // Tausche die Tiles zurück - this.board[fromIndex] = { ...this.board[toIndex] }; - this.board[toIndex] = tempTile; - this.$forceUpdate(); - return false; - } + await this.handleMatches(matches, true); + return true; + } else { + console.log('❌ Move nicht erfolgreich - Kein 3er-Match'); + // Tausche die Tiles zurück + this.board[fromIndex] = { ...this.board[toIndex] }; + this.board[toIndex] = tempTile; + this.$forceUpdate(); + return false; + } }, // Hilfsmethode: Warte für eine bestimmte Zeit @@ -4963,6 +5096,21 @@ export default { animation: newTileAppear 0.5s ease-out forwards; } +/* Raketen-Power-ups */ +.game-tile.rocket-horizontal, +.game-tile.rocket-vertical { + background: white !important; + position: relative; +} + +/* Raketen-Symbol */ +.rocket-symbol { + font-size: 24px; + color: white; + text-shadow: 2px 2px 4px rgba(0,0,0,0.5); + font-weight: bold; +} + @keyframes newTileAppear { 0% { transform: scale(0.1);