var canPlayCard = false; var isInitialized = false; const AlertsComponent = { template: `
`, data() { return { alerts: [] }; }, methods: { alertMessage(message) { const alertData = { id: Date.now(), message: message, isVisible: true, }; this.alerts.push(alertData); setTimeout(() => { this.removeAlert(alertData.id); }, 5000); }, removeAlert(id) { const index = this.alerts.findIndex(alert => alert.id === id); if (index !== -1) { this.alerts.splice(index, 1); } } } }; const PlayerHandComponent = { data() { return { hand: [], isDogPhase: false, isAwaitingResponse: false, }; }, computed: { isHandInactive() { //TODO: Needs implementation } }, template: `
`, methods: { updateHand(eventData) { this.hand = eventData.hand.map(card => ({ idx: parseInt(card.idx, 10), card: card.card })); this.isDogPhase = false; console.log("Vue Data Updated. Hand size:", this.hand.length); if (this.hand.length > 0) { console.log("First card path check:", this.getCardImagePath(this.hand[0].card)); } }, handlePlayCard(cardidx) { if(this.isAwaitingResponse) return if(!canPlayCard) return canPlayCard = false; this.isAwaitingResponse = true console.debug(`Playing card ${cardidx} from hand`) const wiggleKeyframes = [ { transform: 'translateX(0)' }, { transform: 'translateX(-5px)' }, { transform: 'translateX(5px)' }, { transform: 'translateX(-5px)' }, { transform: 'translateX(0)' } ]; const wiggleTiming = { duration: 400, iterations: 1, easing: 'ease-in-out', fill: 'forwards' }; const targetButton = this.$el.querySelector(`[data-card-id="${cardidx}"]`); const cardElement = targetButton ? targetButton.closest('.handcard') : null; const payload = { cardindex: cardidx.toString(), isDog: false } sendEventAndWait("PlayCard", payload).then( () => { this.hand = this.hand.filter(card => card.idx !== cardidx); this.hand.forEach((card, index) => { card.idx = index; }) this.isAwaitingResponse = false; } ).catch( (err) => { if (cardElement) { cardElement.animate(wiggleKeyframes, wiggleTiming); } else { console.warn(`Could not find DOM element for card index ${cardidx} to wiggle.`); } this.isAwaitingResponse = false; canPlayCard = true; } ) }, handleSkipDogLife() { globalThis.handleSkipDogLife(); }, getCardImagePath(cardName) { return `/assets/images/cards/${cardName}.png`; } } }; const ScoreBoardComponent = { data() { return { trumpsuit: 'N/A', playerScores: [], }; }, template: `

Tricks Won

PLAYER
TRICKS
{{ player.name }}
{{ player.tricks }}
`, methods: { calculateNewScores(players, tricklist) { const playercounts = new Map(); players.forEach(player => { playercounts.set(player, 0) }); tricklist.forEach(playerWonTrick => { if (playerWonTrick !== "Trick in Progress" && playercounts.has(playerWonTrick)) { playercounts.set(playerWonTrick, playercounts.get(playerWonTrick) + 1); } }); const newScores = players.map(name => ({ name: name, tricks: playercounts.get(name) || 0, })); newScores.sort((a, b) => b.tricks - a.tricks); return newScores; }, updateNewRoundData(eventData) { console.log("Vue Scoreboard Data Update Triggered: New Round!"); this.playerScores = eventData.players.map(player => ({ name: player, tricks: 0, })); }, updateTrickEndData(eventData) { const { playerwon, playersin, tricklist } = eventData; console.log(`Vue Scoreboard Data Update Triggered: ${playerwon} won the trick!`); this.playerScores = this.calculateNewScores(playersin, tricklist); if (typeof globalThis.alertMessage === 'function') { globalThis.alertMessage(`${playerwon} won the trick!`); } else { console.error("ALERT MESSAGE FAILED: globalThis.alertMessage is NOT a function."); } } } }; const GameInfoComponent = { data() { return { trumpsuit: 'No Trumpsuit', firstCardImagePath: '/assets/images/cards/1B.png', }; }, template: `

Trumpsuit

{{ trumpsuit }}

First Card
First Card
`, methods: { resetFirstCard(eventData) { console.log("GameInfoComponent: Resetting First Card to placeholder."); this.firstCardImagePath = '/assets/images/cards/1B.png'; }, updateFirstCard(eventData) { const firstCardId = eventData.firstCard; console.log("GameInfoComponent: Updating First Card to:", firstCardId); let imageSource; if (firstCardId === "BLANK" || !firstCardId) { imageSource = "/assets/images/cards/1B.png"; } else { imageSource = `/assets/images/cards/${firstCardId}.png`; } this.firstCardImagePath = imageSource; }, updateTrumpsuit(eventData) { this.trumpsuit = eventData.trumpsuit; } } }; const TrickDisplayComponent = { data() { return { playedCards: [], }; }, template: `
{{ play.player }}
`, methods: { getCardImagePath(cardId) { return `/assets/images/cards/${cardId}.png`; }, clearPlayedCards() { console.log("TrickDisplayComponent: Clearing played cards."); this.playedCards = []; }, updatePlayedCards(eventData) { console.log("TrickDisplayComponent: Updating played cards."); this.playedCards = eventData.playedCards; } } }; function formatPlayerName(player) { let name = player.name; if (player.dog) { name += " 🐶"; } return name; } const TurnComponent = { data() { return { currentPlayerName: 'Waiting...', nextPlayers: [], }; }, template: `

Current Player

{{ currentPlayerName }}

Next Players

{{ name }}

`, methods: { updateTurnData(eventData) { console.log("TurnComponent: Updating turn data."); const { currentPlayer, nextPlayers } = eventData; this.currentPlayerName = formatPlayerName(currentPlayer); this.nextPlayers = nextPlayers.map(player => formatPlayerName(player)); } } }; const LobbyComponent = { data() { return { lobbyName: 'Loading...', lobbyId: 'default', isHost: false, maxPlayers: 0, players: [], }; }, template: `
Lobby-Name: {{ lobbyName }}
Exit
Players: {{ players.length }} / {{ maxPlayers }}
`, methods: { updateLobbyData(eventData) { console.log("LobbyComponent: Received Lobby Update Event."); this.isHost = eventData.host; this.maxPlayers = eventData.maxPlayers; this.players = eventData.players; }, setInitialData(name, id) { this.lobbyName = name; this.lobbyId = id; }, startGame() { globalThis.startGame() }, leaveGame(gameId) { //TODO: Needs implementation }, handleKickPlayer(playerId) { globalThis.handleKickPlayer(playerId) } } }; function requestCardEvent(eventData) { const player = eventData.player; const handElement = $('#card-slide') handElement.removeClass('inactive'); canPlayCard = true; } function receiveGameStateChange(eventData) { const content = eventData.content; const title = eventData.title || 'Knockout Whist'; const url = eventData.url || null; exchangeBody(content, title, url); } function receiveLobbyUpdateEvent(eventData) { const host = eventData.host; const maxPlayers = eventData.maxPlayers; const players = eventData.players; const lobbyPlayersContainer = $('#players'); const playerAmountBox = $('#playerAmount'); let newHtml = '' if (host) { players.forEach(user => { const inner = user.self ? `
${user.name} (You)
Remove` : `
${user.name}
Remove
` newHtml += `
Profile
${inner}
` }) } else { players.forEach(user => { const inner = user.self ? `
${user.name} (You)
` : `
${user.name}
` newHtml += `
Profile
${inner}
` }) } lobbyPlayersContainer.html(newHtml); playerAmountBox.text(`Players: ${players.length} / ${maxPlayers}`); } function receiveKickEvent(eventData) { $('#kickedModal').modal({ backdrop: 'static', keyboard: false }).modal('show'); setTimeout(() => { receiveGameStateChange(eventData) }, 5000); } function receiveSessionClosedEvent(eventData) { $('#sessionClosed').modal({ backdrop: 'static', keyboard: false }).modal('show'); setTimeout(() => { receiveGameStateChange(eventData) }, 5000); } function receiveRoundEndEvent(eventData) { const player = eventData.player const tricks = eventData.tricks alertMessage(`${player} won this round with ${tricks} tricks!`) } let playerHandApp = null; let scoreBoardApp = null; let gameInfoApp = null; let trickDisplayApp = null; let turnApp = null; globalThis.initGameVueComponents = function() { // --- 1. CLEANUP (If already initialized) --- if (playerHandApp) { console.log("Updating Hand View. Unmounting previous instance."); playerHandApp.unmount(); globalThis.updatePlayerHand = undefined; onEvent("ReceivedHandEvent", null); } // --- 2. INITIALIZATION/RE-INITIALIZATION --- const app = Vue.createApp(PlayerHandComponent); playerHandApp = app; const mountedHand = app.mount('#player-hand-container'); if (mountedHand && mountedHand.updateHand) { globalThis.updatePlayerHand = mountedHand.updateHand; onEvent("ReceivedHandEvent", globalThis.updatePlayerHand); console.log("PLAYER HAND SYSTEM: updatePlayerHand successfully exposed."); } else { console.error("FATAL ERROR: PlayerHandComponent mount failed. Check if #player-hand-container exists."); } // --- 3. Initialize Scoreboard --- if (scoreBoardApp) return const app2 = Vue.createApp(ScoreBoardComponent) scoreBoardApp = app2 const mountedHand2 = app2.mount('#score-table') if (mountedHand2) { globalThis.updateNewRoundData = mountedHand2.updateNewRoundData; onEvent("NewRoundEvent", handleNewRoundEvent); globalThis.updateTrickEndData = mountedHand2.updateTrickEndData; onEvent("TrickEndEvent", globalThis.updateTrickEndData); console.log("SCOREBOARD: updateNewRoundData successfully exposed."); } else { console.error("FATAL ERROR: Scoreboard mount failed. Check if #score-table exists."); } // --- 4. Initialize Gameinfo --- if (gameInfoApp) return const app3 = Vue.createApp(GameInfoComponent) gameInfoApp = app3 const mountedGameInfo = app3.mount('#game-info-component') if(mountedGameInfo) { globalThis.resetFirstCard = mountedGameInfo.resetFirstCard; globalThis.updateFirstCard = mountedGameInfo.updateFirstCard; globalThis.updateTrumpsuit = mountedGameInfo.updateTrumpsuit onEvent("NewTrickEvent", handleNewTrickEvent); console.log("GameInfo: resetFirstCard successfully exposed."); } else { console.error("FATAL ERROR: GameInfo mount failed. Check if #score-table exists."); } // -- 5. Initialize TrickCardContainer --- if (trickDisplayApp) return; const app4 = Vue.createApp(TrickDisplayComponent); trickDisplayApp = app4; const mountedTrickDisplay = app4.mount('#trick-cards-container'); if (mountedTrickDisplay) { globalThis.clearPlayedCards = mountedTrickDisplay.clearPlayedCards; globalThis.updatePlayedCards = mountedTrickDisplay.updatePlayedCards; onEvent("CardPlayedEvent", handleCardPlayedEvent) console.log("TRICK DISPLAY: Handlers successfully exposed (clearPlayedCards, updatePlayedCards)."); } else { console.error("FATAL ERROR: TrickDisplay mount failed. Check if #trick-cards-container exists."); } // --- 6. Initialize TurnContainer --- if (turnApp) return; const app5 = Vue.createApp(TurnComponent) turnApp = app5; const mountedTurnApp = app5.mount('#turn-component') if(mountedTurnApp) { globalThis.updateTurnData = mountedTurnApp.updateTurnData; onEvent("TurnEvent", globalThis.updateTurnData); console.log("TURN DISPLAY: Handlers successfully exposed (clearPlayedCards, updatePlayedCards)."); } else { console.error("FATAL ERROR: TURNAPP mount failed. Check if #trick-cards-container exists."); } } let lobbyApp = null; globalThis.initLobbyVueComponents = function(initialLobbyName, initialLobbyId, initialIsHost, initialMaxPlayers, initialPlayers) { if (lobbyApp) return; const appLobby = Vue.createApp(LobbyComponent); lobbyApp = appLobby; const mountedLobby = appLobby.mount('#lobby-app-mount'); if (mountedLobby) { mountedLobby.setInitialData(initialLobbyName, initialLobbyId); mountedLobby.updateLobbyData({ host: initialIsHost, maxPlayers: initialMaxPlayers, players: initialPlayers }); globalThis.updateLobbyData = mountedLobby.updateLobbyData; onEvent("LobbyUpdateEvent", globalThis.updateLobbyData); console.log("LobbyComponent successfully mounted and registered events."); } else { console.error("FATAL ERROR: LobbyComponent mount failed."); } } function handleCardPlayedEvent(eventData) { console.log("CardPlayedEvent received. Updating Game Info and Trick Display."); if (typeof globalThis.updateFirstCard === 'function') { globalThis.updateFirstCard(eventData); } if (typeof globalThis.updatePlayedCards === 'function') { globalThis.updatePlayedCards(eventData); } } function handleNewTrickEvent(eventData) { if (typeof globalThis.resetFirstCard === 'function') { globalThis.resetFirstCard(eventData); } if (typeof globalThis.clearPlayedCards === 'function') { globalThis.clearPlayedCards(); } } function handleNewRoundEvent(eventData) { if (typeof globalThis.updateNewRoundData === 'function') { globalThis.updateNewRoundData(eventData); } if (typeof globalThis.updateTrumpsuit === 'function') { globalThis.updateTrumpsuit(eventData); } } onEvent("GameStateChangeEvent", receiveGameStateChange) onEvent("RequestCardEvent", requestCardEvent) onEvent("LeftEvent", receiveGameStateChange) onEvent("KickEvent", receiveKickEvent) onEvent("SessionClosed", receiveSessionClosedEvent) onEvent("RoundEndEvent", receiveRoundEndEvent)