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}`">
|
<template v-for="(tile, index) in board" :key="`tile-${index}`">
|
||||||
<div v-if="isValidPosition(Math.floor(index / boardWidth), index % boardWidth)"
|
<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-index="index"
|
||||||
|
:data-type="tile ? tile.type : null"
|
||||||
@mousedown="onTileMouseDown($event, index)"
|
@mousedown="onTileMouseDown($event, index)"
|
||||||
@mouseup="onTileMouseUp($event, index)"
|
@mouseup="onTileMouseUp($event, index)"
|
||||||
@mousemove="onTileMouseMove($event)"
|
@mousemove="onTileMouseMove($event)"
|
||||||
@@ -98,7 +105,10 @@
|
|||||||
@touchstart="onTileMouseDown($event, index)"
|
@touchstart="onTileMouseDown($event, index)"
|
||||||
@touchend="onTileMouseUp($event, index)"
|
@touchend="onTileMouseUp($event, index)"
|
||||||
@dblclick="handleDoubleClick(index, $event)">
|
@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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -354,25 +364,29 @@ export default {
|
|||||||
this.sounds.bomb = new Audio('/sounds/match3/bomb.wav');
|
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.rocket = new Audio('/sounds/match3/roket.wav'); // Beachte: Datei heißt "roket.wav"
|
||||||
this.sounds.rainbow = new Audio('/sounds/match3/rainbow.wav');
|
this.sounds.rainbow = new Audio('/sounds/match3/rainbow.wav');
|
||||||
|
this.sounds.falling = new Audio('/sounds/match3/falling.wav');
|
||||||
|
|
||||||
// Setze Lautstärke auf 50%
|
// Setze Lautstärke auf 50%
|
||||||
this.sounds.move.volume = 0.5;
|
this.sounds.move.volume = 0.5;
|
||||||
this.sounds.bomb.volume = 0.5;
|
this.sounds.bomb.volume = 0.5;
|
||||||
this.sounds.rocket.volume = 0.5;
|
this.sounds.rocket.volume = 0.5;
|
||||||
this.sounds.rainbow.volume = 0.5;
|
this.sounds.rainbow.volume = 0.5;
|
||||||
|
this.sounds.falling.volume = 0.5;
|
||||||
|
|
||||||
// Warte bis alle Sounds geladen sind
|
// Warte bis alle Sounds geladen sind
|
||||||
Promise.all([
|
Promise.all([
|
||||||
this.sounds.move.load(),
|
this.sounds.move.load(),
|
||||||
this.sounds.bomb.load(),
|
this.sounds.bomb.load(),
|
||||||
this.sounds.rocket.load(),
|
this.sounds.rocket.load(),
|
||||||
this.sounds.rainbow.load()
|
this.sounds.rainbow.load(),
|
||||||
|
this.sounds.falling.load()
|
||||||
]).then(() => {
|
]).then(() => {
|
||||||
console.log('🔊 Sounds erfolgreich geladen:', {
|
console.log('🔊 Sounds erfolgreich geladen:', {
|
||||||
move: this.sounds.move.readyState,
|
move: this.sounds.move.readyState,
|
||||||
bomb: this.sounds.bomb.readyState,
|
bomb: this.sounds.bomb.readyState,
|
||||||
rocket: this.sounds.rocket.readyState,
|
rocket: this.sounds.rocket.readyState,
|
||||||
rainbow: this.sounds.rainbow.readyState
|
rainbow: this.sounds.rainbow.readyState,
|
||||||
|
falling: this.sounds.falling.readyState
|
||||||
});
|
});
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.warn('⚠️ Fehler beim Laden der Sounds:', error);
|
console.warn('⚠️ Fehler beim Laden der Sounds:', error);
|
||||||
@@ -1136,6 +1150,11 @@ export default {
|
|||||||
return matches;
|
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
|
// Hilfsmethode: Prüfe ob 3 Tiles einen gültigen Match bilden
|
||||||
isValidMatch(index1, index2, index3, board) {
|
isValidMatch(index1, index2, index3, board) {
|
||||||
// Prüfe ob alle Indizes gültig sind
|
// Prüfe ob alle Indizes gültig sind
|
||||||
@@ -1352,25 +1371,33 @@ export default {
|
|||||||
|
|
||||||
console.log(`🔧 Verarbeite ${matches.length} Matches...`);
|
console.log(`🔧 Verarbeite ${matches.length} Matches...`);
|
||||||
|
|
||||||
// WICHTIG: Filtere zuerst nach 3er-Matches und entferne Duplikate
|
// Erstelle Power-ups für 4er-Matches (BEVOR die Tiles entfernt werden)
|
||||||
const validMatches = matches.filter(match => match.length === 3);
|
await this.createPowerUpsForMatches(matches);
|
||||||
|
|
||||||
if (validMatches.length === 0) {
|
// Sammle alle zu entfernenden Tile-Indizes (aus 3er-Matches UND 4er-Matches)
|
||||||
console.log('🔧 Keine 3er-Matches zum Verarbeiten');
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`🔧 ${validMatches.length} gültige 3er-Matches gefunden`);
|
console.log(`🔧 ${tilesToRemove.size} Tiles zum Entfernen 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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Starte Schrumpf-Animation für alle zu entfernenden Tiles
|
// Starte Schrumpf-Animation für alle zu entfernenden Tiles
|
||||||
console.log(`🔧 Starte Schrumpf-Animation für ${tilesToRemove.size} 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`);
|
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
|
// Fülle leere Positionen mit neuen Tiles auf
|
||||||
@@ -1556,6 +1586,105 @@ export default {
|
|||||||
console.log(`🔧 ${newTilesAdded} neue Tiles hinzugefügt`);
|
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
|
// Animierte Entfernung von Tiles mit Schrumpf-Effekt
|
||||||
async animateTileRemoval(tileIndices) {
|
async animateTileRemoval(tileIndices) {
|
||||||
console.log(`🎬 Starte Schrumpf-Animation für ${tileIndices.length} Tiles...`);
|
console.log(`🎬 Starte Schrumpf-Animation für ${tileIndices.length} Tiles...`);
|
||||||
@@ -1637,6 +1766,9 @@ export default {
|
|||||||
element.style.transform = `translateY(${fallPixels}px)`;
|
element.style.transform = `translateY(${fallPixels}px)`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Spiele Fall-Sound ab
|
||||||
|
this.playSound('falling');
|
||||||
|
|
||||||
// Warte auf die Fall-Animation (0,5 Sekunden)
|
// Warte auf die Fall-Animation (0,5 Sekunden)
|
||||||
await this.wait(500);
|
await this.wait(500);
|
||||||
|
|
||||||
@@ -1932,22 +2064,23 @@ export default {
|
|||||||
// Warte auf Animation (0,75 Sekunden)
|
// Warte auf Animation (0,75 Sekunden)
|
||||||
await this.wait(750);
|
await this.wait(750);
|
||||||
|
|
||||||
// Prüfe ob der Move einen 3er-Match erzeugt hat
|
// Prüfe ob der Move einen 3er-Match oder 4er-Match erzeugt hat
|
||||||
const matches = this.findMatchesOnBoard(this.board, false);
|
const matches = this.findMatchesOnBoard(this.board, false);
|
||||||
const hasValidMatch = matches.some(match => match.length === 3);
|
const hasValidMatch = matches.some(match => match.length >= 3);
|
||||||
|
|
||||||
|
if (hasValidMatch) {
|
||||||
|
console.log(`✅ Move erfolgreich - ${matches.length} Match(es) gefunden!`);
|
||||||
|
|
||||||
if (hasValidMatch) {
|
await this.handleMatches(matches, true);
|
||||||
console.log('✅ Move erfolgreich - 3er-Match gefunden!');
|
return true;
|
||||||
await this.handleMatches(matches, true);
|
} else {
|
||||||
return true;
|
console.log('❌ Move nicht erfolgreich - Kein 3er-Match');
|
||||||
} else {
|
// Tausche die Tiles zurück
|
||||||
console.log('❌ Move nicht erfolgreich - Kein 3er-Match');
|
this.board[fromIndex] = { ...this.board[toIndex] };
|
||||||
// Tausche die Tiles zurück
|
this.board[toIndex] = tempTile;
|
||||||
this.board[fromIndex] = { ...this.board[toIndex] };
|
this.$forceUpdate();
|
||||||
this.board[toIndex] = tempTile;
|
return false;
|
||||||
this.$forceUpdate();
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Hilfsmethode: Warte für eine bestimmte Zeit
|
// Hilfsmethode: Warte für eine bestimmte Zeit
|
||||||
@@ -4963,6 +5096,21 @@ export default {
|
|||||||
animation: newTileAppear 0.5s ease-out forwards;
|
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 {
|
@keyframes newTileAppear {
|
||||||
0% {
|
0% {
|
||||||
transform: scale(0.1);
|
transform: scale(0.1);
|
||||||
|
|||||||
Reference in New Issue
Block a user