feat(match3): Erweiterung der Power-Up-Mechanik und Verbesserung der Benutzeroberfläche
- 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.
This commit is contained in:
@@ -88,8 +88,15 @@
|
||||
}">
|
||||
<template v-for="(tile, index) in board" :key="`tile-${index}`">
|
||||
<div v-if="isValidPosition(Math.floor(index / boardWidth), index % boardWidth)"
|
||||
:class="['game-tile', { 'empty': !tile, 'dragging': draggedTileIndex === index, 'drag-hover': isDragging && adjacentTilesForHover.includes(index) }]"
|
||||
:class="['game-tile', {
|
||||
'empty': !tile,
|
||||
'dragging': draggedTileIndex === index,
|
||||
'drag-hover': isDragging && adjacentTilesForHover.includes(index),
|
||||
'rocket-horizontal': tile && tile.type === 'rocket-horizontal',
|
||||
'rocket-vertical': tile && tile.type === 'rocket-vertical'
|
||||
}]"
|
||||
:data-index="index"
|
||||
:data-type="tile ? tile.type : null"
|
||||
@mousedown="onTileMouseDown($event, index)"
|
||||
@mouseup="onTileMouseUp($event, index)"
|
||||
@mousemove="onTileMouseMove($event)"
|
||||
@@ -98,7 +105,10 @@
|
||||
@touchstart="onTileMouseDown($event, index)"
|
||||
@touchend="onTileMouseUp($event, index)"
|
||||
@dblclick="handleDoubleClick(index, $event)">
|
||||
<span v-if="tile" class="tile-symbol">{{ getTileSymbol(tile.type) }}</span>
|
||||
<span v-if="tile && !isRocketTile(tile.type)" class="tile-symbol">{{ getTileSymbol(tile.type) }}</span>
|
||||
<span v-else-if="tile && isRocketTile(tile.type)" class="rocket-symbol">
|
||||
🚀
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user