diff --git a/backend/utils/falukant/initializeFalukantTypes.js b/backend/utils/falukant/initializeFalukantTypes.js index 22507e7..e9daa35 100644 --- a/backend/utils/falukant/initializeFalukantTypes.js +++ b/backend/utils/falukant/initializeFalukantTypes.js @@ -14,6 +14,7 @@ import MusicType from "../../models/falukant/type/music.js"; import BanquetteType from "../../models/falukant/type/banquette.js"; import LearnRecipient from "../../models/falukant/type/learn_recipient.js"; import PoliticalOfficeType from "../../models/falukant/type/political_office_type.js"; +import PoliticalOfficeBenefitType from "../../models/falukant/type/political_office_benefit_type.js"; import PoliticalOfficePrerequisite from "../../models/falukant/predefine/political_office_prerequisite.js"; import UndergroundType from "../../models/falukant/type/underground.js"; @@ -32,6 +33,7 @@ export const initializeFalukantTypes = async () => { await initializeFalukantMusicTypes(); await initializeFalukantBanquetteTypes(); await initializeLearnerTypes(); + await initializePoliticalOfficeBenefitTypes(); await initializePoliticalOfficeTypes(); await initializePoliticalOfficePrerequisites(); await initializeUndergroundTypes(); @@ -267,6 +269,17 @@ const learnerTypes = [ { tr: 'director', }, ]; +const politicalOfficeBenefitTypes = [ + { tr: 'salary' }, + { tr: 'reputation' }, + { tr: 'influence' }, + { tr: 'access_level' }, + { tr: 'housing_allowance' }, + { tr: 'tax_exemption' }, + { tr: 'guard_protection' }, + { tr: 'court_immunity' }, +]; + const politicalOffices = [ { tr: "assessor", seatsPerRegion: 10, regionType: "city", termLength: 5 }, { tr: "councillor", seatsPerRegion: 7, regionType: "city", termLength: 7 }, @@ -847,7 +860,16 @@ export const initializeLearnerTypes = async () => { } }); } -} +}; + +export const initializePoliticalOfficeBenefitTypes = async () => { + for (const benefitType of politicalOfficeBenefitTypes) { + await PoliticalOfficeBenefitType.findOrCreate({ + where: { tr: benefitType.tr }, + }); + } + console.log(`[Falukant] PoliticalOfficeBenefitTypes initialisiert: ${politicalOfficeBenefitTypes.length} Typen`); +}; export const initializePoliticalOfficeTypes = async () => { for (const po of politicalOffices) { diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js index 8e1d327..df2609a 100644 --- a/frontend/src/store/index.js +++ b/frontend/src/store/index.js @@ -121,7 +121,6 @@ const store = createStore({ const daemonSocket = new WebSocket(import.meta.env.VITE_DAEMON_SOCKET); daemonSocket.onopen = () => { - console.log('Daemon WebSocket connected'); const payload = JSON.stringify({ event: 'setUserId', data: { userId: state.user.id } diff --git a/frontend/src/utils/axios.js b/frontend/src/utils/axios.js index 5f46d1a..d84fae1 100644 --- a/frontend/src/utils/axios.js +++ b/frontend/src/utils/axios.js @@ -10,15 +10,10 @@ const apiClient = axios.create({ apiClient.interceptors.request.use(config => { const user = store.getters.user; - console.log('🔑 Axios Interceptor - User:', user); if (user && user.authCode) { config.headers['userid'] = user.id; config.headers['authcode'] = user.authCode; // Kleinschreibung! - console.log('📡 Setze Headers:', { - userid: user.id, - authcode: user.authCode - }); } else { console.log('⚠ Keine User-Daten verfĂŒgbar'); } diff --git a/frontend/src/views/minigames/Match3Game.vue b/frontend/src/views/minigames/Match3Game.vue index 8f121b4..f2c0c2a 100644 --- a/frontend/src/views/minigames/Match3Game.vue +++ b/frontend/src/views/minigames/Match3Game.vue @@ -90,11 +90,9 @@
+ @mousedown="startDrag(index, $event)" + @touchstart="startDrag(index, $event)" + @dblclick="handleDoubleClick(index, $event)"> {{ getTileSymbol(tile.type) }}
@@ -113,6 +111,23 @@

Board Layout: {{ boardLayout ? 'Ja' : 'Nein' }}

+ + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
@@ -206,6 +221,8 @@ + + @@ -236,7 +253,8 @@ export default { boardLayout: [], // Neue: Array der Level-Form boardWidth: 6, // Standardwert fĂŒr 6x6 boardHeight: 6, // Standardwert fĂŒr 6x6 - tileTypes: ['gem', 'star', 'heart'], + tileTypes: ['gem', 'star', 'heart'], // Normale Tiles (ohne Power-Ups) + powerUpTypes: ['rocket', 'bomb', 'rainbow'], // Power-Up Tiles (werden nur durch Matches erstellt) // Game progress score: 0, @@ -277,7 +295,18 @@ export default { apiCallCooldown: 1000, // 1 Sekunde Cooldown zwischen API-Aufrufen // WICHTIG: Verhindere mehrfache Level-Initialisierung - isInitializingLevel: false + isInitializingLevel: false, + + // Animation states + showExplosion: false, + explosionPosition: { x: 0, y: 0 }, + showRocketFlight: false, + rocketStartPos: { x: 0, y: 0 }, + rocketEndPos: { x: 0, y: 0 }, + showRainbowEffect: false, + rainbowCenter: { x: 0, y: 0 }, + showBombEffect: false, + bombCenter: { x: 0, y: 0 } }; return initialData; @@ -631,17 +660,25 @@ export default { // ZusĂ€tzliche PrĂŒfung: Stelle sicher, dass keine initialen Matches vorhanden sind // WICHTIG: Reduziere Debug-Ausgaben fĂŒr bessere Performance - const finalCheck = this.findMatchesOnBoard(this.board, false); // false = keine Debug-Ausgaben - if (finalCheck.length > 0) { - // WICHTIG: Generiere das Board erneut, anstatt die Matches zu verarbeiten - // Bei Level-Initialisierung sollen keine Punkte vergeben oder Level-Objekte geprĂŒft werden - this.board = this.generateBoardFromLayout(); - - // PrĂŒfe erneut auf Matches (rekursiv, aber mit Limit) - const secondCheck = this.findMatchesOnBoard(this.board, false); - if (secondCheck.length > 0) { - // Board hat immer noch initiale Matches - verwende es wie es ist + let attempts = 0; + const maxAttempts = 10; // Verhindere Endlosschleifen + + while (attempts < maxAttempts) { + const matches = this.findMatchesOnBoard(this.board, false); // false = keine Debug-Ausgaben + if (matches.length === 0) { + break; // Keine Matches mehr vorhanden } + + console.log(`🔄 Initiale Matches gefunden (Versuch ${attempts + 1}):`, matches.length); + + // RĂ€ume die Matches auf, aber ohne Punkte zu vergeben + this.cleanupInitialMatches(matches); + + attempts++; + } + + if (attempts >= maxAttempts) { + console.warn('⚠ Konnte initiale Matches nicht vollstĂ€ndig aufrĂ€umen nach', maxAttempts, 'Versuchen'); } // WICHTIG: Setze das Spiel als aktiv, aber prĂŒfe NICHT sofort die Level-Objekte @@ -654,7 +691,12 @@ export default { }, startDrag(index, event) { - if (!this.gameActive) { + if (!this.gameActive || this.isDragging) { + return; + } + + // Verhindere Drag wĂ€hrend der Fall-Animation + if (this.isFalling) { return; } @@ -665,6 +707,14 @@ export default { // Drag-Effekt starten this.startDragEffect(event, index); + + // Verhindere Standard-Browser-Verhalten + event.preventDefault(); + event.stopPropagation(); + + // Registriere Document-Level Event-Handler fĂŒr bessere Kontrolle + document.addEventListener('mouseup', this.handleDocumentMouseUp, true); + document.addEventListener('touchend', this.handleDocumentTouchEnd, true); }, endDrag(event) { @@ -689,16 +739,12 @@ export default { const endY = event.clientY || event.changedTouches?.[0]?.clientY; if (endX && endY) { - const deltaX = endX - this.dragStartX; - const deltaY = endY - this.dragStartY; + // Finde das Tile, ĂŒber dem der Drag endet + const targetIndex = this.findTileAtPosition(endX, endY); - // Bestimme die Richtung des Drags - const direction = this.getDragDirection(deltaX, deltaY); - - if (direction) { - const targetIndex = this.getAdjacentIndex(this.dragStartIndex, direction); - - if (targetIndex !== null) { + if (targetIndex !== null && targetIndex !== this.dragStartIndex) { + // PrĂŒfe ob die Tiles benachbart sind + if (this.areTilesAdjacent(this.dragStartIndex, targetIndex)) { this.swapTiles(this.dragStartIndex, targetIndex); } } @@ -711,22 +757,70 @@ export default { this.isDragging = false; }, - getDragDirection(deltaX, deltaY) { - const minDragDistance = 30; // Minimale Drag-Distanz fĂŒr einen gĂŒltigen Zug - - if (Math.abs(deltaX) < minDragDistance && Math.abs(deltaY) < minDragDistance) { - return null; // Zu kurzer Drag + // Neue Methode: Drag abbrechen (z.B. bei mouseleave) + cancelDrag() { + if (!this.isDragging || this.dragStartIndex === null) { + return; } - if (Math.abs(deltaX) > Math.abs(deltaY)) { - // Horizontale Bewegung - return deltaX > 0 ? 'right' : 'left'; - } else { - // Vertikale Bewegung - return deltaY > 0 ? 'down' : 'up'; + // Drag-Effekt beenden ohne Swap + this.endDragEffect(); + + // Reset drag state + this.dragStartIndex = null; + this.dragStartX = null; + this.dragStartY = null; + this.isDragging = false; + }, + + // Document-Level Event-Handler fĂŒr bessere Kontrolle + handleDocumentMouseUp(event) { + if (this.isDragging) { + this.endDrag(event); } }, + handleDocumentTouchEnd(event) { + if (this.isDragging) { + this.endDrag(event); + } + }, + + + + // Neue Methode: Finde das Tile an einer bestimmten Bildschirm-Position + findTileAtPosition(clientX, clientY) { + // Durchsuche alle Tiles und finde das, das an der Position liegt + for (let i = 0; i < this.board.length; i++) { + if (this.board[i] && !this.board[i].isSpecial) { + const tileElement = document.querySelector(`[data-index="${i}"]`); + if (tileElement) { + const rect = tileElement.getBoundingClientRect(); + if (clientX >= rect.left && clientX <= rect.right && + clientY >= rect.top && clientY <= rect.bottom) { + return i; + } + } + } + } + return null; + }, + + // Neue Methode: PrĂŒfe ob zwei Tiles benachbart sind + areTilesAdjacent(index1, index2) { + if (index1 === null || index2 === null) return false; + + const { row: row1, col: col1 } = this.indexToCoords(index1); + const { row: row2, col: col2 } = this.indexToCoords(index2); + + // Tiles sind benachbart wenn sie sich in der gleichen Zeile oder Spalte befinden + // und nur 1 Position voneinander entfernt sind + const rowDiff = Math.abs(row1 - row2); + const colDiff = Math.abs(col1 - col2); + + return (rowDiff === 1 && colDiff === 0) || (rowDiff === 0 && colDiff === 1); + }, + getAdjacentIndex(index, direction) { if (index === null) return null; @@ -773,8 +867,8 @@ export default { if (index1 !== null && index2 !== null && index3 !== null && this.isValidPosition(row, col) && this.isValidPosition(row, col + 1) && this.isValidPosition(row, col + 2) && board[index1] && board[index2] && board[index3] && - board[index1].type === board[index2].type && - board[index2].type === board[index3].type) { + this.canTilesMatch(board[index1], board[index2]) && + this.canTilesMatch(board[index2], board[index3])) { // Erweitere das Match nach links und rechts let startCol = col; @@ -783,7 +877,7 @@ export default { while (startCol > 0) { const leftIndex = this.coordsToIndex(row, startCol - 1); if (leftIndex !== null && this.isValidPosition(row, startCol - 1) && - board[leftIndex] && board[leftIndex].type === board[index1].type) { + board[leftIndex] && this.canTilesMatch(board[leftIndex], board[index1])) { startCol--; } else { break; @@ -793,7 +887,7 @@ export default { while (endCol < this.boardWidth - 1) { const rightIndex = this.coordsToIndex(row, endCol + 1); if (rightIndex !== null && this.isValidPosition(row, endCol + 1) && - board[rightIndex] && board[rightIndex].type === board[rightIndex].type) { + board[rightIndex] && this.canTilesMatch(board[rightIndex], board[index1])) { endCol++; } else { break; @@ -827,8 +921,8 @@ export default { if (index1 !== null && index2 !== null && index3 !== null && this.isValidPosition(row, col) && this.isValidPosition(row + 1, col) && this.isValidPosition(row + 2, col) && board[index1] && board[index2] && board[index3] && - board[index1].type === board[index2].type && - board[index2].type === board[index3].type) { + this.canTilesMatch(board[index1], board[index2]) && + this.canTilesMatch(board[index2], board[index3])) { // Erweitere das Match nach oben und unten let startRow = row; @@ -837,7 +931,7 @@ export default { while (startRow > 0) { const upIndex = this.coordsToIndex(startRow - 1, col); if (upIndex !== null && this.isValidPosition(startRow - 1, col) && - board[upIndex] && board[upIndex].type === board[index1].type) { + board[upIndex] && this.canTilesMatch(board[upIndex], board[index1])) { startRow--; } else { break; @@ -847,7 +941,7 @@ export default { while (endRow < this.boardHeight - 1) { const downIndex = this.coordsToIndex(endRow + 1, col); if (downIndex !== null && this.isValidPosition(endRow + 1, col) && - board[downIndex] && board[downIndex].type === board[downIndex].type) { + board[downIndex] && this.canTilesMatch(board[downIndex], board[index1])) { endRow++; } else { break; @@ -870,9 +964,126 @@ export default { } } + // L-Form Matches finden + const lShapeMatches = this.findLShapesOnBoard(board); + matches.push(...lShapeMatches); + return matches; }, + // Neue Methode: Finde L-Form Matches auf dem Brett + findLShapesOnBoard(board) { + const lShapeMatches = []; + + // Durchsuche das Brett nach L-Formen + for (let row = 0; row < this.boardHeight - 2; row++) { + for (let col = 0; col < this.boardWidth - 2; col++) { + // PrĂŒfe verschiedene L-Form Muster + const patterns = this.checkLShapePatterns(board, row, col); + lShapeMatches.push(...patterns); + } + } + + return lShapeMatches; + }, + + // Hilfsmethode: PrĂŒfe L-Form Muster an einer bestimmten Position + checkLShapePatterns(board, startRow, startCol) { + const patterns = []; + + // Pattern 1: Horizontale Linie nach rechts + vertikale Linie nach unten + // xxx + // x + // x + const horizontalRight = this.checkHorizontalLine(board, startRow, startCol, 3); + if (horizontalRight.length >= 3) { + const verticalDown = this.checkVerticalLine(board, startRow, startCol, 3); + if (verticalDown.length >= 2) { + const lShape = [...horizontalRight, ...verticalDown]; + if (lShape.length >= 5) { + patterns.push(lShape); + } + } + } + + // Pattern 2: Horizontale Linie nach rechts + vertikale Linie nach oben + // x + // x + // xxx + const horizontalRight2 = this.checkHorizontalLine(board, startRow + 2, startCol, 3); + if (horizontalRight2.length >= 3) { + const verticalUp = this.checkVerticalLine(board, startRow, startCol, 3); + if (verticalUp.length >= 2) { + const lShape = [...horizontalRight2, ...verticalUp]; + if (lShape.length >= 5) { + patterns.push(lShape); + } + } + } + + // Pattern 3: Horizontale Linie nach links + vertikale Linie nach unten + // xxx + // x + // x + const horizontalLeft = this.checkHorizontalLine(board, startRow, startCol - 2, 3); + if (horizontalLeft.length >= 3) { + const verticalDown2 = this.checkVerticalLine(board, startRow, startCol, 3); + if (verticalDown2.length >= 2) { + const lShape = [...horizontalLeft, ...verticalDown2]; + if (lShape.length >= 5) { + patterns.push(lShape); + } + } + } + + // Pattern 4: Horizontale Linie nach links + vertikale Linie nach oben + // x + // x + // xxx + const horizontalLeft2 = this.checkHorizontalLine(board, startRow + 2, startCol - 2, 3); + if (horizontalLeft2.length >= 3) { + const verticalUp2 = this.checkVerticalLine(board, startRow, startCol, 3); + if (verticalUp2.length >= 2) { + const lShape = [...horizontalLeft2, ...verticalUp2]; + if (lShape.length >= 5) { + patterns.push(lShape); + } + } + } + + return patterns; + }, + + // Hilfsmethode: PrĂŒfe horizontale Linie + checkHorizontalLine(board, row, startCol, minLength) { + const line = []; + for (let col = startCol; col < startCol + minLength && col < this.boardWidth; col++) { + const index = this.coordsToIndex(row, col); + if (index !== null && this.isValidPosition(row, col) && board[index] && + (line.length === 0 || this.canTilesMatch(board[line[0]], board[index]))) { + line.push(index); + } else { + break; + } + } + return line; + }, + + // Hilfsmethode: PrĂŒfe vertikale Linie + checkVerticalLine(board, startRow, col, minLength) { + const line = []; + for (let row = startRow; row < startRow + minLength && row < this.boardHeight; row++) { + const index = this.coordsToIndex(row, col); + if (index !== null && this.isValidPosition(row, col) && board[index] && + (line.length === 0 || this.canTilesMatch(board[line[0]], board[index]))) { + line.push(index); + } else { + break; + } + } + return line; + }, + fixInitialMatches() { let attempts = 0; const maxAttempts = 50; @@ -942,6 +1153,8 @@ export default { // UrsprĂŒngliches Tile ausblenden originalTile.style.opacity = '0.3'; + + }, updateDragEffect(event) { @@ -963,11 +1176,16 @@ export default { document.removeEventListener('mousemove', this.boundMouseMoveHandler); this.boundMouseMoveHandler = null; } - if (this.boundTouchMoveHandler) { + if (this.boundTouchMoveHandler) { document.removeEventListener('touchmove', this.boundTouchMoveHandler); this.boundTouchMoveHandler = null; } + // Document-Level Event-Handler entfernen + document.removeEventListener('mouseup', this.handleDocumentMouseUp, true); + document.removeEventListener('touchend', this.handleDocumentTouchEnd, true); + + // Drag-Element entfernen if (this.dragElement) { document.body.removeChild(this.dragElement); @@ -988,23 +1206,27 @@ export default { if (this.isFalling) { return; } - - // Tausche die Tiles const temp = this.board[index1]; this.board[index1] = this.board[index2]; this.board[index2] = temp; - // Erhöhe den Zug-ZĂ€hler + // WICHTIG: PrĂŒfe auf Regenbogen-Tile Tausch (vor normalen Matches) + if (this.handleRainbowSwap(temp, this.board[index1])) { + // Regenbogen-Tausch erfolgreich - Zug-ZĂ€hler wird bereits in handleRainbowSwap erhöht + return; // Beende hier, da Regenbogen-Tausch bereits verarbeitet wurde + } + + // Erhöhe den Zug-ZĂ€hler nur fĂŒr normale Swaps this.moves++; this.movesLeft--; - // PrĂŒfe auf Matches nach dem Swap + // PrĂŒfe auf normale Matches nach dem Swap const matches = this.findMatchesOnBoard(this.board, false); // Reduziere Debug-Ausgaben if (matches.length > 0) { - this.handleMatches(matches); + this.handleMatches(matches, true); // true = Spieler-Zug } else { // Keine Matches - tausche zurĂŒck const tempBack = this.board[index1]; @@ -1024,24 +1246,43 @@ export default { } }, - handleMatches(matches) { + handleMatches(matches, isPlayerMove = false) { const matchedIndices = new Set(); + const powerUpIndices = new Set(); // Sammle Power-Up Tiles separat + matches.forEach(match => { - match.forEach(index => matchedIndices.add(index)); + match.forEach(index => { + // PrĂŒfe ob es sich um ein Power-Up Tile handelt + if (this.isPowerUpTile(this.board[index])) { + powerUpIndices.add(index); + } else { + matchedIndices.add(index); + } + }); }); - // Matched tiles markieren + // Matched tiles markieren (nur normale Tiles) this.matchedTiles = Array.from(matchedIndices); - // Matches zĂ€hlen (jeder Match erhöht den ZĂ€hler) - this.matchesMade += 1; + // Matches zĂ€hlen (nur bei Spieler-ZĂŒgen, nicht bei automatischen Kaskaden) + if (isPlayerMove) { + this.matchesMade += 1; + } - // Punkte hinzufĂŒgen + // Punkte hinzufĂŒgen (nur fĂŒr normale Tiles) const matchPoints = matchedIndices.size * 10 * this.currentLevel; this.levelScore += matchPoints; this.score += matchPoints; - // Nach kurzer Verzögerung Fall-Animation starten + // WICHTIG: Erstelle spezielle Items basierend auf der Match-Anordnung + const powerUpPositions = this.createSpecialItems(matches); + + // Entferne Power-Up Positionen aus den gematchten Indizes + powerUpPositions.forEach(pos => { + matchedIndices.delete(pos); + }); + + // Nach kurzer Verzögerung Fall-Animation starten (nur fĂŒr normale Tiles) setTimeout(() => { this.startFallAnimation(Array.from(matchedIndices)); }, 500); @@ -1240,7 +1481,7 @@ export default { if (newMatches.length > 0) { // Starte neue Match-Verarbeitung // Diese wird automatisch wieder checkForCascadeMatches aufrufen - this.handleMatches(newMatches); + this.handleMatches(newMatches, false); // false = automatischer Match } else { // Wichtig: Auch ohne neue Matches Level-Objekte prĂŒfen // (falls der Spieler bereits alle Ziele erreicht hat) @@ -1272,8 +1513,16 @@ export default { // PrĂŒfe ob an dieser Position ein Feld existiert if (currentIndex !== null && this.isValidPosition(row, col)) { // Suche nach dem nĂ€chsten freien Platz von unten - while (targetRow >= 0 && !this.isValidPosition(targetRow, col)) { - targetRow--; + while (targetRow >= 0) { + // WICHTIG: PrĂŒfe ob es sich um ein freies Feld (o) handelt + if (!this.isValidPosition(targetRow, col) || + (this.boardLayout[targetRow] && this.boardLayout[targetRow][col] && + this.boardLayout[targetRow][col].type === 'empty')) { + // Freies Feld oder ungĂŒltige Position - ĂŒberspringe es + targetRow--; + continue; + } + break; } if (targetRow >= 0) { @@ -1305,6 +1554,12 @@ export default { if (this.isValidPosition(row, col)) { const index = this.coordsToIndex(row, col); if (index !== null && !this.board[index]) { + // WICHTIG: PrĂŒfe ob es sich um ein freies Feld (o) handelt + if (this.boardLayout[row] && this.boardLayout[row][col] && + this.boardLayout[row][col].type === 'empty') { + // Freies Feld - ĂŒberspringe es + continue; + } firstEmptyRow = row; break; } @@ -1317,6 +1572,13 @@ export default { if (this.isValidPosition(row, col)) { const index = this.coordsToIndex(row, col); if (index !== null && !this.board[index]) { + // WICHTIG: PrĂŒfe ob es sich um ein freies Feld (o) handelt + if (this.boardLayout[row] && this.boardLayout[row][col] && + this.boardLayout[row][col].type === 'empty') { + // Freies Feld - ĂŒberspringe es + continue; + } + // Erstelle ein neues Tile const tileType = this.tileTypes[Math.floor(Math.random() * this.tileTypes.length)]; const newTile = { @@ -1693,7 +1955,10 @@ export default { circle: '⭕', square: '🟩', crown: '👑', - rainbow: '🌈' + rainbow: '🌈', + // Spezielle Items + rocket: '🚀', + bomb: '💣' }; return symbols[type] || '❓'; }, @@ -1750,7 +2015,7 @@ export default { // Kein Feld - null hinzufĂŒgen board.push(null); } else if (layoutCell.type === 'tile') { - // Tile-Feld - zufĂ€lliger Tile-Typ + // Tile-Feld - zufĂ€lliger Tile-Typ (nur normale Tiles, keine Power-Ups) const tileType = this.tileTypes[Math.floor(Math.random() * this.tileTypes.length)]; const tile = { type: tileType, @@ -1830,6 +2095,21 @@ export default { return true; }, + // Hilfsmethode: PrĂŒfe ob ein Tile ein Power-Up ist + isPowerUpTile(tile) { + return tile && this.powerUpTypes.includes(tile.type); + }, + + // Hilfsmethode: PrĂŒfe ob zwei Tiles matchen können + canTilesMatch(tile1, tile2) { + // Power-Up Tiles können nicht mit anderen Tiles matchen (auch nicht mit sich selbst) + if (this.isPowerUpTile(tile1) || this.isPowerUpTile(tile2)) { + return false; + } + // Normale Tiles können nur mit dem gleichen Typ matchen + return tile1 && tile2 && tile1.type === tile2.type; + }, + // Hilfsmethode: Zeige Fortschritt fĂŒr Level-Objekte getObjectiveProgress(objective) { switch (objective.type) { @@ -1842,6 +2122,1069 @@ export default { default: return 'N/A'; } + }, + + // Neue Methode: Erstelle spezielle Items basierend auf der Match-Anordnung + createSpecialItems(matches) { + const powerUpPositions = new Set(); // Sammle Positionen, wo Power-Ups erstellt werden + + matches.forEach(match => { + if (match.length >= 4) { + // Mindestens 4 Felder gematcht - prĂŒfe auf spezielle Anordnungen + const specialItem = this.detectSpecialItemPattern(match); + if (specialItem) { + // Erstelle das spezielle Item an der ersten Position des Matches + const centerIndex = match[Math.floor(match.length / 2)]; + this.createSpecialItem(centerIndex, specialItem); + powerUpPositions.add(centerIndex); + } + } + }); + + // Entferne Power-Up Positionen aus den Matches, damit sie nicht als normale Matches behandelt werden + return powerUpPositions; + }, + + // Neue Methode: Erkenne spezielle Item-Muster + detectSpecialItemPattern(match) { + if (match.length < 4) return null; + + // Konvertiere Match-Indizes zu Koordinaten + const coords = match.map(index => this.indexToCoords(index)); + + console.log('🔍 Pattern-Erkennung fĂŒr Match:', coords); + console.log('🔍 Anzahl Tiles:', coords.length); + + // WICHTIG: PrĂŒfe zuerst die komplexesten Muster (höchste PrioritĂ€t) + // UND stelle sicher, dass nur EIN Pattern erkannt wird + + // PrĂŒfe auf I-Form (Regenbogen) - höchste PrioritĂ€t, mindestens 5 Tiles + if (coords.length >= 5 && this.isIRainbow(coords)) { + console.log('✅ Rainbow Pattern erkannt'); + return 'rainbow'; + } + + // PrĂŒfe auf L-Form (Bombe) - zweite PrioritĂ€t, mindestens 5 Tiles + if (coords.length >= 5 && this.isLBomb(coords)) { + console.log('💣 L-Bomb Pattern erkannt'); + return 'bomb'; + } + + // PrĂŒfe auf T-Form (Bombe) - dritte PrioritĂ€t, mindestens 5 Tiles + if (coords.length >= 5 && this.isTBomb(coords)) { + console.log('💣 T-Bomb Pattern erkannt'); + return 'bomb'; + } + + // PrĂŒfe auf Kreuz-Form (Bombe) - vierte PrioritĂ€t, mindestens 5 Tiles + if (coords.length >= 5 && this.isCrossBomb(coords)) { + console.log('💣 Cross-Bomb Pattern erkannt'); + return 'bomb'; + } + + // PrĂŒfe auf horizontale Anordnung (Rakete) - niedrigste PrioritĂ€t, mindestens 4 Tiles + if (coords.length >= 4 && this.isHorizontalRocket(coords)) { + console.log('🚀 Horizontal Rocket Pattern erkannt'); + return 'rocket'; + } + + // PrĂŒfe auf vertikale Anordnung (Rakete) - niedrigste PrioritĂ€t, mindestens 4 Tiles + if (coords.length >= 4 && this.isVerticalRocket(coords)) { + console.log('🚀 Vertical Rocket Pattern erkannt'); + return 'rocket'; + } + + console.log('❌ Kein spezielles Pattern erkannt'); + return null; + }, + + // Hilfsmethode: PrĂŒfe auf horizontale Rakete (4+ Felder in einer Reihe) + isHorizontalRocket(coords) { + if (coords.length < 4) return false; + + // Alle Koordinaten mĂŒssen in der gleichen Zeile sein + const row = coords[0].row; + return coords.every(coord => coord.row === row); + }, + + // Hilfsmethode: PrĂŒfe auf vertikale Rakete (4+ Felder in einer Spalte) + isVerticalRocket(coords) { + if (coords.length < 4) return false; + + // Alle Koordinaten mĂŒssen in der gleichen Spalte sein + const col = coords[0].col; + return coords.every(coord => coord.col === col); + }, + + // Hilfsmethode: PrĂŒfe auf L-Form Bombe + isLBomb(coords) { + // L-Form Bombe benötigt mindestens 5 Tiles + if (coords.length < 5) { + return false; + } + + // FĂŒr 5+ Tiles: Suche nach L-Form mit langen Zeilen/Spalten + const rows = new Set(coords.map(c => c.row)); + const cols = new Set(coords.map(c => c.col)); + + // L-Form: Mindestens 3 Zeilen UND 3 Spalten + if (rows.size >= 3 && cols.size >= 3) { + // PrĂŒfe ob es eine echte L-Form gibt + return this.hasLShape(coords); + } + + return false; + }, + + // Hilfsmethode: PrĂŒfe auf T-Form Bombe + isTBomb(coords) { + if (coords.length < 5) return false; // Mindestens 5 Tiles fĂŒr T-Form Bombe + + // Suche nach T-Form: 3+ horizontal + 3+ vertikal + const rows = new Set(coords.map(c => c.row)); + const cols = new Set(coords.map(c => c.col)); + + // Mindestens 3 Zeilen und 3 Spalten + if (rows.size >= 3 && cols.size >= 3) { + // PrĂŒfe ob es eine T-Form gibt + return this.hasTShape(coords); + } + + return false; + }, + + // Hilfsmethode: PrĂŒfe auf Kreuz-Form Bombe + isCrossBomb(coords) { + if (coords.length < 5) return false; // Mindestens 5 Tiles fĂŒr Kreuz-Form Bombe + + // Suche nach Kreuz-Form: 3+ horizontal + 3+ vertikal + const rows = new Set(coords.map(c => c.row)); + const cols = new Set(coords.map(c => c.col)); + + // Mindestens 3 Zeilen und 3 Spalten + if (rows.size >= 3 && cols.size >= 3) { + // PrĂŒfe ob es eine Kreuz-Form gibt + return this.hasCrossShape(coords); + } + + return false; + }, + + // Hilfsmethode: PrĂŒfe auf I-Form Regenbogen (5+ Felder in einer Reihe/Spalte) + isIRainbow(coords) { + if (coords.length < 5) return false; + + // Alle Koordinaten mĂŒssen in der gleichen Zeile ODER Spalte sein + const row = coords[0].row; + const col = coords[0].col; + + return coords.every(coord => coord.row === row) || + coords.every(coord => coord.col === col); + }, + + + + // Hilfsmethode: PrĂŒfe auf L-Form + hasLShape(coords) { + if (coords.length < 5) return false; // Mindestens 5 Tiles fĂŒr L-Form + + // Gruppiere nach Zeilen und Spalten + const rowGroups = {}; + const colGroups = {}; + + coords.forEach(coord => { + if (!rowGroups[coord.row]) rowGroups[coord.row] = []; + if (!colGroups[coord.col]) colGroups[coord.col] = []; + rowGroups[coord.row].push(coord); + colGroups[coord.col].push(coord); + }); + + // PrĂŒfe auf L-Form: Eine Zeile mit 3+ Tiles UND eine Spalte mit 3+ Tiles + // und sie mĂŒssen sich an einer Ecke treffen + const rows = Object.keys(rowGroups).map(r => parseInt(r)); + const cols = Object.keys(colGroups).map(c => parseInt(c)); + + // Finde Zeilen mit mindestens 3 Tiles + const longRows = rows.filter(row => rowGroups[row].length >= 3); + // Finde Spalten mit mindestens 2 Tiles (fĂŒr L-Form reicht das) + const longCols = cols.filter(col => colGroups[col].length >= 2); + + // PrĂŒfe ob eine lange Zeile und eine lange Spalte sich schneiden + for (const row of longRows) { + for (const col of longCols) { + // PrĂŒfe ob es ein Tile an der Kreuzung gibt + const intersection = coords.find(coord => coord.row === row && coord.col === col); + if (intersection) { + // PrĂŒfe ob es wirklich eine L-Form ist + const rowTiles = rowGroups[row]; + const colTiles = colGroups[col]; + + // L-Form: horizontale Linie + vertikale Linie, die sich treffen + if (rowTiles.length >= 3 && colTiles.length >= 2) { + return true; + } + } + } + } + + return false; + }, + + // Hilfsmethode: PrĂŒfe auf T-Form + hasTShape(coords) { + if (coords.length < 5) return false; // Mindestens 5 Tiles fĂŒr T-Form + + // Gruppiere nach Zeilen und Spalten + const rowGroups = {}; + const colGroups = {}; + + coords.forEach(coord => { + if (!rowGroups[coord.row]) rowGroups[coord.row] = []; + if (!colGroups[coord.col]) colGroups[coord.col] = []; + rowGroups[coord.row].push(coord); + colGroups[coord.col].push(coord); + }); + + // Finde Zeilen und Spalten mit mindestens 3 Tiles + const longRows = Object.values(rowGroups).filter(group => group.length >= 3); + const longCols = Object.values(colGroups).filter(group => group.length >= 3); + + // T-Form: mindestens eine lange Zeile UND eine lange Spalte + // ZusĂ€tzlich: mindestens ein Tile muss an der Kreuzung sein + if (longRows.length > 0 && longCols.length > 0) { + // PrĂŒfe ob es eine Kreuzung gibt + const intersection = coords.filter(coord => + longRows.some(row => row.some(r => r.row === coord.row)) && + longCols.some(col => col.some(c => c.col === coord.col)) + ); + + return intersection.length > 0; + } + return false; + }, + + // Hilfsmethode: PrĂŒfe auf Kreuz-Form + hasCrossShape(coords) { + if (coords.length < 5) return false; // Mindestens 5 Tiles fĂŒr Kreuz-Form + + // Gruppiere nach Zeilen und Spalten + const rowGroups = {}; + const colGroups = {}; + + coords.forEach(coord => { + if (!rowGroups[coord.row]) rowGroups[coord.row] = []; + if (!colGroups[coord.col]) colGroups[coord.col] = []; + rowGroups[coord.row].push(coord); + colGroups[coord.col].push(coord); + }); + + // Finde Zeilen und Spalten mit mindestens 3 Tiles + const longRows = Object.values(rowGroups).filter(group => group.length >= 3); + const longCols = Object.values(colGroups).filter(group => group.length >= 3); + + // Kreuz-Form: mindestens eine lange Zeile UND eine lange Spalte + // ZusĂ€tzlich: mindestens ein Tile muss an der Kreuzung sein + if (longRows.length > 0 && longCols.length > 0) { + // PrĂŒfe ob es eine Kreuzung gibt + const intersection = coords.filter(coord => + longRows.some(row => row.some(r => r.row === coord.row)) && + longCols.some(col => col.some(c => c.col === coord.col)) + ); + + return intersection.length > 0; + } + return false; + }, + + // Neue Methode: Erstelle ein spezielles Item + createSpecialItem(index, itemType) { + if (!this.board[index]) return; + + // Erstelle das spezielle Item + const specialItem = { + type: itemType, + id: Date.now() + index + Math.random(), + row: this.indexToCoords(index).row, + col: this.indexToCoords(index).col, + isSpecial: true, + specialType: itemType + }; + + // Ersetze das normale Tile mit dem speziellen Item + this.board[index] = specialItem; + + // Zeige eine visuelle BestĂ€tigung + this.showSpecialItemCreated(index, itemType); + + console.log(`🎯 Spezielles Item erstellt: ${itemType} an Position ${index}`); + }, + + // Neue Methode: Zeige visuelle BestĂ€tigung fĂŒr erstelltes spezielles Item + showSpecialItemCreated(index, itemType) { + const tileElement = document.querySelector(`[data-index="${index}"]`); + if (tileElement) { + // FĂŒge eine spezielle Klasse hinzu + tileElement.classList.add('special-item-created'); + + // Zeige eine kurze Animation + setTimeout(() => { + tileElement.classList.remove('special-item-created'); + }, 1000); + } + }, + + // Neue Methode: ZĂ€hle Power-Up als Zug + countPowerUpMove() { + this.moves++; + this.movesLeft--; + console.log('🔱 Power-Up als Zug gezĂ€hlt:', 'moves:', this.moves, 'movesLeft:', this.movesLeft); + }, + + // Neue Methode: Behandle Doppelklick auf Power-Up Tiles + handleDoubleClick(index, event) { + event.preventDefault(); + event.stopPropagation(); + + const tile = this.board[index]; + if (!tile) return; + + if (tile.isSpecial) { + // Power-Up als Zug zĂ€hlen + this.countPowerUpMove(); + + if (tile.type === 'rainbow') { + // Regenbogen-Tile: ZufĂ€lligen Tile-Typ entfernen + const randomTileType = this.tileTypes[Math.floor(Math.random() * this.tileTypes.length)]; + this.removeAllTilesOfType(randomTileType); + console.log(`🌈 Regenbogen-Doppelklick: Alle ${randomTileType} Tiles entfernt`); + } else if (tile.type === 'bomb') { + // Bomben-Tile: 9 Tiles rundherum entfernen + this.explodeBomb(index, 1); // 1 Ring = 3x3 Bereich + console.log(`💣 Bomben-Doppelklick: 9 Tiles rundherum entfernt`); + } else if (tile.type === 'rocket') { + // Raketen-Tile: 4 Nachbarfelder löschen und Rakete starten + this.handleRocketDoubleClick(index); + console.log(`🚀 Raketen-Doppelklick: 4 Nachbarfelder gelöscht und Rakete gestartet`); + } + } + }, + + // Neue Methode: Entferne alle Tiles eines bestimmten Typs + removeAllTilesOfType(tileType) { + const tilesToRemove = []; + + // Sammle alle Indizes der zu entfernenden Tiles + for (let i = 0; i < this.board.length; i++) { + if (this.board[i] && this.board[i].type === tileType) { + tilesToRemove.push(i); + } + } + + if (tilesToRemove.length > 0) { + // Zeige Regenbogen-Effekt-Animation fĂŒr das erste Tile + if (tilesToRemove.length > 0) { + this.showRainbowEffectAnimation(tilesToRemove[0]); + } + + // Entferne die Tiles + tilesToRemove.forEach(index => { + this.board[index] = null; + }); + + // Starte Fall-Animation + this.startFallAnimation(tilesToRemove); + + // Punkte hinzufĂŒgen + const points = tilesToRemove.length * 20 * this.currentLevel; + this.levelScore += points; + this.score += points; + } + }, + + // Neue Methode: Behandle Power-Up Tile Tausch + handleRainbowSwap(originalTile1, originalTile2) { + // Power-Up Tausch als Zug zĂ€hlen (wird auch ĂŒber swapTiles aufgerufen) + this.countPowerUpMove(); + + if (originalTile1.type === 'rainbow' && originalTile2.type === 'rainbow') { + // Zwei Regenbogen-Tiles getauscht: Entferne alle Tiles + this.removeAllTilesFromBoard(); + return true; + } else if (originalTile1.type === 'rainbow' || originalTile2.type === 'rainbow') { + // Ein Regenbogen-Tile mit normalem Tile getauscht: Entferne alle Tiles des normalen Typs + const normalTile = originalTile1.type === 'rainbow' ? originalTile2 : originalTile1; + this.removeAllTilesOfType(normalTile.type); + return true; + } else if (originalTile1.type === 'bomb' && originalTile2.type === 'bomb') { + // Zwei Bomben-Tiles getauscht: Entferne 2 Ringe (5x5 Bereich) + const bombIndex = this.findBombIndex(originalTile1, originalTile2); + if (bombIndex !== null) { + this.explodeBomb(bombIndex, 2); // 2 Ringe = 5x5 Bereich + } + return true; + } else if (originalTile1.type === 'bomb' || originalTile2.type === 'bomb') { + // Ein Bomben-Tile mit normalem Tile getauscht: Entferne 9 Tiles rund um das Ziel + const bombTile = originalTile1.type === 'bomb' ? originalTile1 : originalTile2; + const targetTile = originalTile1.type === 'bomb' ? originalTile2 : originalTile1; + + // Finde die neue Position der Bombe + const newBombIndex = this.findTileIndex(bombTile); + if (newBombIndex !== null) { + this.explodeBomb(newBombIndex, 1); // 1 Ring = 3x3 Bereich + } + return true; + } else if ((originalTile1.type === 'bomb' && originalTile2.type === 'rainbow') || + (originalTile1.type === 'rainbow' && originalTile2.type === 'bomb')) { + // Bombe + Regenbogen: Erstelle zufĂ€llige Bomben und löse sie aus + this.createRandomBombs(20); + setTimeout(() => { + this.detonateAllBombs(); + }, 100); // Kurze Verzögerung fĂŒr visuellen Effekt + return true; + } else if (originalTile1.type === 'rocket' && originalTile2.type === 'rocket') { + // Zwei Raketen verbunden: Lösche Nachbarfelder beider Raketen und starte 3 Raketen + this.handleRocketConnection(originalTile1, originalTile2); + return true; + } else if ((originalTile1.type === 'rocket' && originalTile2.type === 'rainbow') || + (originalTile1.type === 'rainbow' && originalTile2.type === 'rocket')) { + // Rakete + Regenbogen: Erstelle zufĂ€llige Raketen und starte sie + this.createRandomRockets(10); + setTimeout(() => { + this.launchAllRockets(); + }, 100); // Kurze Verzögerung fĂŒr visuellen Effekt + return true; + } else if ((originalTile1.type === 'rocket' && originalTile2.type === 'bomb') || + (originalTile1.type === 'bomb' && originalTile2.type === 'rocket')) { + // Rakete + Bombe: Lösche 4 Nachbarfelder und starte Bomben-Rakete + this.handleRocketBombCombination(originalTile1, originalTile2); + return true; + } else if (originalTile1.type === 'rocket' || originalTile2.type === 'rocket') { + // Eine Rakete mit normalem Tile: Rakete fliegt auf zufĂ€lliges Feld + const rocketTile = originalTile1.type === 'rocket' ? originalTile1 : originalTile2; + const targetTile = originalTile1.type === 'rocket' ? originalTile2 : originalTile1; + this.handleRocketLaunch(rocketTile, targetTile); + return true; + } + return false; // Kein Power-Up Tausch + }, + + // Hilfsmethode: Finde den Index einer Bombe nach dem Tausch + findBombIndex(originalBomb1, originalBomb2) { + // Suche nach der Bombe auf dem Board + for (let i = 0; i < this.board.length; i++) { + if (this.board[i] && this.board[i].type === 'bomb') { + return i; + } + } + return null; + }, + + // Hilfsmethode: Finde den Index eines Tiles nach dem Tausch + findTileIndex(originalTile) { + // Suche nach dem Tile auf dem Board + for (let i = 0; i < this.board.length; i++) { + if (this.board[i] && this.board[i].id === originalTile.id) { + return i; + } + } + return null; + }, + + // Neue Methode: Entferne alle Tiles vom Board + removeAllTilesFromBoard() { + const allTileIndices = []; + + // Sammle alle Tile-Indizes + for (let i = 0; i < this.board.length; i++) { + if (this.board[i]) { + allTileIndices.push(i); + } + } + + if (allTileIndices.length > 0) { + // Zeige Regenbogen-Effekt-Animation in der Mitte des Boards + const centerIndex = Math.floor(this.board.length / 2); + this.showRainbowEffectAnimation(centerIndex); + + // Entferne alle Tiles + allTileIndices.forEach(index => { + this.board[index] = null; + }); + + // Starte Fall-Animation + this.startFallAnimation(allTileIndices); + + // Punkte hinzufĂŒgen + const points = allTileIndices.length * 30 * this.currentLevel; + this.levelScore += points; + this.score += points; + + console.log(`🌈 Alle Tiles vom Board entfernt!`); + } + }, + + // Neue Methode: Explodiere Bombe mit bestimmter Reichweite + explodeBomb(centerIndex, rings) { + const { row: centerRow, col: centerCol } = this.indexToCoords(centerIndex); + const tilesToRemove = []; + + // Sammle alle Tiles in den angegebenen Ringen + for (let ring = 0; ring <= rings; ring++) { + for (let r = centerRow - ring; r <= centerRow + ring; r++) { + for (let c = centerCol - ring; c <= centerCol + ring; c++) { + if (r >= 0 && r < this.boardHeight && c >= 0 && c < this.boardWidth) { + const index = this.coordsToIndex(r, c); + if (index !== null && this.board[index]) { + tilesToRemove.push(index); + } + } + } + } + } + + if (tilesToRemove.length > 0) { + // Zeige Explosions-Animation + this.showExplosionAnimation(centerIndex); + + // Entferne die Tiles + tilesToRemove.forEach(index => { + this.board[index] = null; + }); + + // Starte Fall-Animation + this.startFallAnimation(tilesToRemove); + + // Punkte hinzufĂŒgen + const points = tilesToRemove.length * 25 * this.currentLevel; + this.levelScore += points; + this.score += points; + + console.log(`💣 Bombe explodiert: ${tilesToRemove.length} Tiles entfernt`); + } + }, + + // Neue Methode: Erstelle zufĂ€llige Bomben auf 20% der Tiles + createRandomBombs(percentage = 20) { + const totalTiles = this.board.filter(tile => tile !== null).length; + const bombCount = Math.floor(totalTiles * percentage / 100); + const bombIndices = []; + + // Sammle alle verfĂŒgbaren Tile-Indizes + const availableIndices = []; + for (let i = 0; i < this.board.length; i++) { + if (this.board[i] && this.board[i].type !== 'bomb') { + availableIndices.push(i); + } + } + + // WĂ€hle zufĂ€llige Tiles aus und verwandle sie in Bomben + for (let i = 0; i < Math.min(bombCount, availableIndices.length); i++) { + const randomIndex = Math.floor(Math.random() * availableIndices.length); + const tileIndex = availableIndices.splice(randomIndex, 1)[0]; + + // Verwandle das Tile in eine Bombe + this.board[tileIndex] = { + type: 'bomb', + id: Date.now() + tileIndex + Math.random(), + row: this.indexToCoords(tileIndex).row, + col: this.indexToCoords(tileIndex).col, + isSpecial: true, + specialType: 'bomb' + }; + + bombIndices.push(tileIndex); + } + + console.log(`💣 ${bombIndices.length} zufĂ€llige Bomben erstellt`); + return bombIndices; + }, + + // Neue Methode: Löse alle Bomben auf dem Feld aus + detonateAllBombs() { + const bombIndices = []; + + // Sammle alle Bomben-Indizes + for (let i = 0; i < this.board.length; i++) { + if (this.board[i] && this.board[i].type === 'bomb') { + bombIndices.push(i); + } + } + + // Löse alle Bomben aus + bombIndices.forEach(index => { + this.explodeBomb(index, 1); + }); + + console.log(`💣 Alle ${bombIndices.length} Bomben wurden ausgelöst!`); + }, + + // Neue Methode: Erstelle zufĂ€llige Raketen + createRandomRockets(percentage = 10) { + const totalTiles = this.board.filter(tile => tile !== null).length; + const rocketCount = Math.floor(totalTiles * percentage / 100); + const availableIndices = []; + + // Sammle alle verfĂŒgbaren Positionen (keine Power-Ups) + for (let i = 0; i < this.board.length; i++) { + if (this.board[i] && !this.isPowerUpTile(this.board[i])) { + availableIndices.push(i); + } + } + + // WĂ€hle zufĂ€llige Positionen fĂŒr Raketen + for (let i = 0; i < Math.min(rocketCount, availableIndices.length); i++) { + const randomIndex = Math.floor(Math.random() * availableIndices.length); + const boardIndex = availableIndices.splice(randomIndex, 1)[0]; + + // Ersetze das Tile durch eine Rakete + this.board[boardIndex] = { + id: Math.random().toString(36).substr(2, 9), + type: 'rocket', + isSpecial: true + }; + } + + console.log(`🚀 ${Math.min(rocketCount, availableIndices.length)} zufĂ€llige Raketen erstellt`); + }, + + // Neue Methode: Starte alle Raketen + launchAllRockets() { + const rocketIndices = []; + + // Finde alle Raketen auf dem Board + for (let i = 0; i < this.board.length; i++) { + if (this.board[i] && this.board[i].type === 'rocket') { + rocketIndices.push(i); + } + } + + // Starte jede Rakete + rocketIndices.forEach(rocketIndex => { + this.handleRocketDoubleClick(rocketIndex); + }); + + console.log(`🚀 Alle ${rocketIndices.length} Raketen wurden gestartet!`); + }, + + // Neue Methode: Behandle Rakete + Bombe Kombination + handleRocketBombCombination(originalTile1, originalTile2) { + // Finde die Position einer der Power-Ups (fĂŒr die 4 Nachbarfelder) + const rocketTile = originalTile1.type === 'rocket' ? originalTile1 : originalTile2; + const bombTile = originalTile1.type === 'bomb' ? originalTile1 : originalTile2; + + // Finde die Position auf dem Board + const rocketIndex = this.findTileIndex(rocketTile); + if (rocketIndex !== null) { + // Lösche die 4 Nachbarfelder der Rakete + const { row: rocketRow, col: rocketCol } = this.indexToCoords(rocketIndex); + const tilesToRemove = []; + + // Sammle die 4 Nachbarfelder (oben, unten, links, rechts) + const directions = [ + { row: rocketRow - 1, col: rocketCol }, // Oben + { row: rocketRow + 1, col: rocketCol }, // Unten + { row: rocketRow, col: rocketCol - 1 }, // Links + { row: rocketRow, col: rocketCol + 1 } // Rechts + ]; + + directions.forEach(({ row, col }) => { + if (row >= 0 && row < this.boardHeight && col >= 0 && col < this.boardWidth) { + const index = this.coordsToIndex(row, col); + if (this.board[index]) { + tilesToRemove.push(index); + } + } + }); + + // Entferne die Nachbarfelder + if (tilesToRemove.length > 0) { + tilesToRemove.forEach(index => { + this.board[index] = null; + }); + + // Starte Fall-Animation + this.startFallAnimation(tilesToRemove); + + // Punkte hinzufĂŒgen + const points = tilesToRemove.length * 25 * this.currentLevel; + this.levelScore += points; + this.score += points; + } + } + + // Starte die Bomben-Rakete nach kurzer Verzögerung + setTimeout(() => { + this.launchBombRocket(); + }, 200); + + console.log(`🚀💣 Rakete + Bombe Kombination: 4 Nachbarfelder gelöscht, Bomben-Rakete gestartet`); + }, + + // Neue Methode: Starte eine Bomben-Rakete + launchBombRocket() { + // Finde alle verfĂŒgbaren Positionen fĂŒr die Landung + const availableIndices = []; + for (let i = 0; i < this.board.length; i++) { + if (this.board[i]) { + availableIndices.push(i); + } + } + + if (availableIndices.length > 0) { + // WĂ€hle zufĂ€llige Landungsposition + const randomIndex = Math.floor(Math.random() * availableIndices.length); + const landingIndex = availableIndices[randomIndex]; + + // Explodiere am Landungsort (9 Felder = 1 Ring) + this.explodeBomb(landingIndex, 1); + + console.log(`🚀💣 Bomben-Rakete landete bei Index ${landingIndex} und explodierte!`); + } + }, + + // Neue Methode: Zeige Explosions-Animation + showExplosionAnimation(centerIndex) { + // Warte bis das DOM gerendert ist + this.$nextTick(() => { + const tileElement = document.querySelector(`[data-index="${centerIndex}"]`); + const gameBoard = document.querySelector('.game-board-container'); + + if (tileElement && gameBoard) { + const tileRect = tileElement.getBoundingClientRect(); + const boardRect = gameBoard.getBoundingClientRect(); + + // Berechne relative Position zum Game-Board-Container + this.explosionPosition = { + x: tileRect.left - boardRect.left + tileRect.width / 2 - 40, + y: tileRect.top - boardRect.top + tileRect.height / 2 - 40 + }; + + this.showExplosion = true; + + // Verstecke Animation nach der Dauer + setTimeout(() => { + this.showExplosion = false; + }, 800); + } + }); + }, + + // Neue Methode: Zeige Raketen-Flug-Animation + showRocketFlightAnimation(startIndex, endIndex) { + // Warte bis das DOM gerendert ist + this.$nextTick(() => { + const startElement = document.querySelector(`[data-index="${startIndex}"]`); + const endElement = document.querySelector(`[data-index="${endIndex}"]`); + const gameBoard = document.querySelector('.game-board-container'); + + if (startElement && endElement && gameBoard) { + const startRect = startElement.getBoundingClientRect(); + const endRect = endElement.getBoundingClientRect(); + const boardRect = gameBoard.getBoundingClientRect(); + + this.rocketStartPos = { + x: startRect.left - boardRect.left + startRect.width / 2 - 20, + y: startRect.top - boardRect.top + startRect.height / 2 - 20 + }; + + this.rocketEndPos = { + x: endRect.left - boardRect.left + endRect.width / 2 - 20, + y: endRect.top - boardRect.top + endRect.height / 2 - 20 + }; + + this.showRocketFlight = true; + + // Verstecke Animation nach der Dauer + setTimeout(() => { + this.showRocketFlight = false; + }, 1200); + } + }); + }, + + // Neue Methode: Zeige Regenbogen-Effekt-Animation + showRainbowEffectAnimation(centerIndex) { + // Warte bis das DOM gerendert ist + this.$nextTick(() => { + const tileElement = document.querySelector(`[data-index="${centerIndex}"]`); + const gameBoard = document.querySelector('.game-board-container'); + + if (tileElement && gameBoard) { + const tileRect = tileElement.getBoundingClientRect(); + const boardRect = gameBoard.getBoundingClientRect(); + + this.rainbowCenter = { + x: tileRect.left - boardRect.left + tileRect.width / 2 - 50, + y: tileRect.top - boardRect.top + tileRect.height / 2 - 50 + }; + + this.showRainbowEffect = true; + + // Verstecke Animation nach der Dauer + setTimeout(() => { + this.showRainbowEffect = false; + }, 1500); + } + }); + }, + + // Neue Methode: Zeige Bomben-Effekt-Animation + showBombEffectAnimation(centerIndex) { + // Warte bis das DOM gerendert ist + this.$nextTick(() => { + const tileElement = document.querySelector(`[data-index="${centerIndex}"]`); + const gameBoard = document.querySelector('.game-board-container'); + + if (tileElement && gameBoard) { + const tileRect = tileElement.getBoundingClientRect(); + const boardRect = gameBoard.getBoundingClientRect(); + + this.bombCenter = { + x: tileRect.left - boardRect.left + tileRect.width / 2 - 30, + y: tileRect.top - boardRect.top + tileRect.height / 2 - 30 + }; + + this.showBombEffect = true; + + // Verstecke Animation nach der Dauer + setTimeout(() => { + this.showBombEffect = false; + }, 300); + } + }); + }, + + // Neue Methode: Behandle Doppelklick auf Rakete + handleRocketDoubleClick(rocketIndex) { + const { row: rocketRow, col: rocketCol } = this.indexToCoords(rocketIndex); + const tilesToRemove = []; + + // Sammle die 4 Nachbarfelder (oben, unten, links, rechts) + const directions = [ + { row: rocketRow - 1, col: rocketCol }, // Oben + { row: rocketRow + 1, col: rocketCol }, // Unten + { row: rocketRow, col: rocketCol - 1 }, // Links + { row: rocketRow, col: rocketCol + 1 } // Rechts + ]; + + directions.forEach(({ row, col }) => { + if (row >= 0 && row < this.boardHeight && col >= 0 && col < this.boardWidth) { + const index = this.coordsToIndex(row, col); + if (index !== null && this.board[index]) { + tilesToRemove.push(index); + } + } + }); + + if (tilesToRemove.length > 0) { + // Zeige Bomben-Effekt-Animation fĂŒr die Rakete + this.showBombEffectAnimation(rocketIndex); + + // Entferne die Nachbar-Tiles + tilesToRemove.forEach(index => { + this.board[index] = null; + }); + + // Starte Fall-Animation + this.startFallAnimation(tilesToRemove); + + // Punkte hinzufĂŒgen + const points = tilesToRemove.length * 15 * this.currentLevel; + this.levelScore += points; + this.score += points; + + // Starte Rakete auf zufĂ€lliges Feld + this.launchRocketToRandomField(rocketIndex); + } + }, + + // Neue Methode: Rakete auf zufĂ€lliges Feld starten + launchRocketToRandomField(rocketIndex) { + // Finde alle verfĂŒgbaren Felder (außer der Rakete selbst) + const availableFields = []; + for (let i = 0; i < this.board.length; i++) { + if (i !== rocketIndex && this.board[i] && this.isValidPosition(this.indexToCoords(i).row, this.indexToCoords(i).col)) { + availableFields.push(i); + } + } + + if (availableFields.length > 0) { + // WĂ€hle ein zufĂ€lliges Feld aus + const randomIndex = availableFields[Math.floor(Math.random() * availableFields.length)]; + + // Zeige visuellen Effekt auf dem Ziel-Tile (kurzer Farbwechsel) + this.showRocketTargetEffect(randomIndex); + + // Kurze Verzögerung, damit der Effekt sichtbar ist + setTimeout(() => { + // Entferne die Rakete vom ursprĂŒnglichen Feld + this.board[rocketIndex] = null; + + // Lösche das zufĂ€llige Feld + this.board[randomIndex] = null; + + // Zeige Raketen-Flug-Animation + this.showRocketFlightAnimation(rocketIndex, randomIndex); + + // Starte Fall-Animation fĂŒr beide Felder + this.startFallAnimation([rocketIndex, randomIndex]); + }, 200); // 200ms Verzögerung fĂŒr den visuellen Effekt + + // Punkte hinzufĂŒgen + const points = 20 * this.currentLevel; + this.levelScore += points; + this.score += points; + + console.log(`🚀 Rakete gestartet und auf Feld ${randomIndex} gelandet!`); + } + }, + + // Neue Methode: Zeige visuellen Effekt auf dem Raketen-Ziel + showRocketTargetEffect(targetIndex) { + const targetTile = document.querySelector(`[data-index="${targetIndex}"]`); + if (targetTile) { + // FĂŒge eine CSS-Klasse fĂŒr den Raketen-Treffer-Effekt hinzu + targetTile.classList.add('rocket-target-hit'); + + // Entferne die Klasse nach der Animation + setTimeout(() => { + targetTile.classList.remove('rocket-target-hit'); + }, 300); + } + }, + + // Neue Methode: Behandle Verbindung zweier Raketen + handleRocketConnection(rocket1, rocket2) { + const rocket1Index = this.findTileIndex(rocket1); + const rocket2Index = this.findTileIndex(rocket2); + + if (rocket1Index === null || rocket2Index === null) return; + + const tilesToRemove = []; + + // Sammle Nachbarfelder beider Raketen + [rocket1Index, rocket2Index].forEach(rocketIndex => { + const { row: rocketRow, col: rocketCol } = this.indexToCoords(rocketIndex); + + const directions = [ + { row: rocketRow - 1, col: rocketCol }, // Oben + { row: rocketRow + 1, col: rocketCol }, // Unten + { row: rocketRow, col: rocketCol - 1 }, // Links + { row: rocketRow, col: rocketCol + 1 } // Rechts + ]; + + directions.forEach(({ row, col }) => { + if (row >= 0 && row < this.boardHeight && col >= 0 && col < this.boardWidth) { + const index = this.coordsToIndex(row, col); + if (index !== null && this.board[index]) { + tilesToRemove.push(index); + } + } + }); + }); + + if (tilesToRemove.length > 0) { + // Entferne die Nachbar-Tiles + tilesToRemove.forEach(index => { + this.board[index] = null; + }); + + // Starte Fall-Animation + this.startFallAnimation(tilesToRemove); + + // Punkte hinzufĂŒgen + const points = tilesToRemove.length * 20 * this.currentLevel; + this.levelScore += points; + this.score += points; + + // Starte 3 Raketen auf zufĂ€llige Felder + this.launchThreeRockets([rocket1Index, rocket2Index]); + } + }, + + // Neue Methode: Starte 3 Raketen auf zufĂ€llige Felder + launchThreeRockets(rocketIndices) { + // Entferne die ursprĂŒnglichen Raketen + rocketIndices.forEach(index => { + this.board[index] = null; + }); + + // Finde alle verfĂŒgbaren Felder + const availableFields = []; + for (let i = 0; i < this.board.length; i++) { + if (!rocketIndices.includes(i) && this.board[i] && this.isValidPosition(this.indexToCoords(i).row, this.indexToCoords(i).col)) { + availableFields.push(i); + } + } + + if (availableFields.length > 0) { + // WĂ€hle 3 zufĂ€llige Felder aus + const selectedFields = []; + for (let i = 0; i < Math.min(3, availableFields.length); i++) { + const randomIndex = Math.floor(Math.random() * availableFields.length); + const fieldIndex = availableFields.splice(randomIndex, 1)[0]; + selectedFields.push(fieldIndex); + } + + // Lösche die ausgewĂ€hlten Felder + selectedFields.forEach(index => { + this.board[index] = null; + }); + + // Starte Fall-Animation fĂŒr alle betroffenen Felder + this.startFallAnimation([...rocketIndices, ...selectedFields]); + + // Punkte hinzufĂŒgen + const points = (rocketIndices.length + selectedFields.length) * 25 * this.currentLevel; + this.levelScore += points; + this.score += points; + + console.log(`🚀🚀🚀 3 Raketen gestartet und auf Felder ${selectedFields.join(', ')} gelandet!`); + } + }, + + // Neue Methode: Rakete mit normalem Tile tauschen + handleRocketLaunch(rocketTile, targetTile) { + const targetIndex = this.findTileIndex(targetTile); + if (targetIndex === null) return; + + const { row: targetRow, col: targetCol } = this.indexToCoords(targetIndex); + const tilesToRemove = []; + + // Sammle die 4 Nachbarfelder des Ziel-Tiles + const directions = [ + { row: targetRow - 1, col: targetCol }, // Oben + { row: targetRow + 1, col: targetCol }, // Unten + { row: targetRow, col: targetCol - 1 }, // Links + { row: targetRow, col: targetCol + 1 } // Rechts + ]; + + directions.forEach(({ row, col }) => { + if (row >= 0 && row < this.boardHeight && col >= 0 && col < this.boardWidth) { + const index = this.coordsToIndex(row, col); + if (index !== null && this.board[index]) { + tilesToRemove.push(index); + } + } + }); + + if (tilesToRemove.length > 0) { + // Entferne die Nachbar-Tiles + tilesToRemove.forEach(index => { + this.board[index] = null; + }); + + // Starte Fall-Animation + this.startFallAnimation(tilesToRemove); + + // Punkte hinzufĂŒgen + const points = tilesToRemove.length * 15 * this.currentLevel; + this.levelScore += points; + this.score += points; + + // Starte Rakete auf zufĂ€lliges Feld + this.launchRocketToRandomField(targetIndex); + } } }, computed: { @@ -2122,6 +3465,7 @@ export default { border: 1px solid #ddd; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; + position: relative; /* FĂŒr absolute Positionierung der Animationen */ } .game-board { @@ -2134,8 +3478,8 @@ export default { } .game-tile { - width: 50px; - height: 50px; + width: 30px; + height: 30px; background: linear-gradient(135deg, #eeeded, #f1eeee); border: 2px solid #c0c0c0; border-radius: 8px; @@ -2147,16 +3491,30 @@ export default { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); pointer-events: auto; user-select: none; + position: relative; } +/* VergrĂ¶ĂŸere den klickbaren Bereich fĂŒr besseres Drag&Drop */ +.game-tile::before { + content: ''; + position: absolute; + top: -5px; + left: -5px; + right: -5px; + bottom: -5px; + z-index: -1; +} + + + .game-tile.empty { background: transparent; border: 2px solid transparent; box-shadow: none; pointer-events: none; /* Wichtig: Behalte die gleiche GrĂ¶ĂŸe wie normale Tiles */ - width: 50px; - height: 50px; + width: 30px; + height: 30px; /* Optional: Zeige einen subtilen Rahmen fĂŒr bessere Sichtbarkeit */ background: rgba(0, 0, 0, 0.05); } @@ -2183,6 +3541,18 @@ export default { z-index: 200; } +/* Spezielle Items */ +.game-tile.special-item-created { + animation: specialItemCreated 1s ease-in-out; + box-shadow: 0 0 20px rgba(255, 215, 0, 0.8); +} + +@keyframes specialItemCreated { + 0% { transform: scale(1); } + 50% { transform: scale(1.3) rotate(10deg); } + 100% { transform: scale(1); } +} + /* Fall-Animation */ .game-tile.falling { transition: transform 0.6s ease-in; @@ -2216,12 +3586,12 @@ export default { } .drag-tile .tile-icon { - font-size: 24px; + font-size: 18px; user-select: none; } .tile-icon { - font-size: 24px; + font-size: 18px; user-select: none; } @@ -2274,6 +3644,135 @@ export default { 100% { transform: scale(1); } } +/* Power-Up Animationen */ +@keyframes explosion { + 0% { + transform: scale(0.1); + opacity: 1; + } + 50% { + transform: scale(1.5); + opacity: 0.8; + } + 100% { + transform: scale(2.5); + opacity: 0; + } +} + +@keyframes rocketFlight { + 0% { + transform: translate(0, 0) rotate(0deg); + opacity: 1; + } + 50% { + transform: translate(var(--dx), var(--dy)) rotate(45deg); + opacity: 0.8; + } + 100% { + transform: translate(calc(var(--dx) * 2), calc(var(--dy) * 2)) rotate(90deg); + opacity: 0; + } +} + +@keyframes rainbowPulse { + 0% { + transform: scale(1) rotate(0deg); + filter: hue-rotate(0deg); + } + 25% { + transform: scale(1.2) rotate(90deg); + filter: hue-rotate(90deg); + } + 50% { + transform: scale(1.4) rotate(180deg); + filter: hue-rotate(180deg); + } + 75% { + transform: scale(1.2) rotate(270deg); + filter: hue-rotate(270deg); + } + 100% { + transform: scale(1) rotate(360deg); + filter: hue-rotate(360deg); + } +} + +@keyframes bombShake { + 0%, 100% { transform: translateX(0) rotate(0deg); } + 25% { transform: translateX(-2px) rotate(-1deg); } + 75% { transform: translateX(2px) rotate(1deg); } +} + +@keyframes rocketTargetHit { + 0% { + background: linear-gradient(135deg, #eeeded, #f1eeee); + transform: scale(1); + } + 25% { + background: linear-gradient(135deg, #ff6b6b, #ff8e53); + transform: scale(1.1); + } + 50% { + background: linear-gradient(135deg, #ffd93d, #ff6b6b); + transform: scale(1.2); + } + 75% { + background: linear-gradient(135deg, #ff8e53, #ffd93d); + transform: scale(1.1); + } + 100% { + background: linear-gradient(135deg, #eeeded, #f1eeee); + transform: scale(1); + } +} + +/* Power-Up Animation Container */ +.power-up-animation { + position: absolute; + pointer-events: none; + z-index: 1000; + transform: translate(-50%, -50%); /* Zentriere die Animation */ +} + +/* Raketen-Treffer-Effekt */ +.game-tile.rocket-target-hit { + animation: rocketTargetHit 0.3s ease-in-out; + z-index: 100; +} + +.explosion-effect { + width: 80px; + height: 80px; + background: radial-gradient(circle, #ff6b6b, #ff8e53, #ffd93d, #6bcf7f); + border-radius: 50%; + animation: explosion 0.8s ease-out forwards; +} + +.rocket-flight { + width: 40px; + height: 40px; + background: linear-gradient(45deg, #ff6b6b, #ffd93d); + clip-path: polygon(50% 0%, 0% 100%, 100% 100%); + animation: rocketFlight 1.2s ease-in-out forwards; +} + +.rainbow-effect { + width: 100px; + height: 100px; + background: conic-gradient(from 0deg, #ff6b6b, #ffd93d, #6bcf7f, #4ecdc4, #45b7d1, #96ceb4, #feca57, #ff9ff3, #ff6b6b); + border-radius: 50%; + animation: rainbowPulse 1.5s ease-in-out forwards; +} + +.bomb-effect { + width: 60px; + height: 60px; + background: radial-gradient(circle, #ff6b6b, #ff8e53); + border-radius: 50%; + animation: bombShake 0.3s ease-in-out infinite; +} + /* Loading State */ .game-board-loading { display: flex; @@ -2304,8 +3803,8 @@ export default { } .game-tile { - width: 45px; - height: 45px; + width: 25px; + height: 25px; } .game-board { @@ -2314,7 +3813,7 @@ export default { } .tile-icon { - font-size: 18px; + font-size: 14px; } .moves-left-display {