import { NgClass, NgIf, CurrencyPipe, CommonModule } from '@angular/common'; import { HttpClient } from '@angular/common/http'; import { FormsModule } from '@angular/forms'; import { ChangeDetectionStrategy, Component, ElementRef, inject, OnInit, signal, ViewChild, } from '@angular/core'; import { AnimatedNumberComponent } from '@blackjack/components/animated-number/animated-number.component'; import { catchError, finalize } from 'rxjs/operators'; import { of } from 'rxjs'; import { AuthService } from '@service/auth.service'; import { AudioService } from '@shared/services/audio.service'; import { CoinflipGame, CoinflipRequest } from './models/coinflip.model'; @Component({ selector: 'app-coinflip', standalone: true, imports: [AnimatedNumberComponent, CurrencyPipe, FormsModule, CommonModule, NgIf, NgClass], templateUrl: './coinflip.component.html', styleUrl: './coinflip.component.css', changeDetection: ChangeDetectionStrategy.OnPush, }) export default class CoinflipComponent implements OnInit { currentBet = signal(10); balance = signal(0); gameInProgress = signal(false); isActionInProgress = signal(false); gameResult = signal(null); betInputValue = signal(10); errorMessage = signal(''); isInvalidBet = signal(false); @ViewChild('coinElement') coinElement?: ElementRef; audioService = inject(AudioService); authService = inject(AuthService); private http = inject(HttpClient); private coinflipSound?: HTMLAudioElement; ngOnInit(): void { // Abonniere Benutzerupdates fuer Echtzeitaktualisierungen des Guthabens this.authService.userSubject.subscribe((user) => { if (user) { this.balance.set(user.balance); } }); // Initialisiere Muenzwurf-Sound this.coinflipSound = new Audio('/sounds/coinflip.mp3'); } setBetAmount(percentage: number) { const newBet = Math.floor(this.balance() * percentage); this.betInputValue.set(newBet > 0 ? newBet : 1); this.currentBet.set(this.betInputValue()); } updateBet(event: Event) { const inputElement = event.target as HTMLInputElement; let value = Number(inputElement.value); // Setze ungueltigen Einsatz-Status zurueck this.isInvalidBet.set(false); // Erzwinge Mindesteinsatz von 1 if (value <= 0) { value = 1; } // Begrenze Einsatz auf verfuegbares Guthaben und zeige Feedback if (value > this.balance()) { value = this.balance(); // Visuelles Feedback anzeigen this.isInvalidBet.set(true); // Zeige den Fehler kurz an setTimeout(() => this.isInvalidBet.set(false), 800); // Aktualisiere das Eingabefeld direkt, um dem Benutzer den maximalen Wert anzuzeigen inputElement.value = String(value); } // Aktualisiere Signale this.betInputValue.set(value); this.currentBet.set(value); } betHeads() { this.placeBet('HEAD'); } betTails() { this.placeBet('TAILS'); } private placeBet(side: 'HEAD' | 'TAILS') { if (this.gameInProgress() || this.isActionInProgress()) return; // Setze vorheriges Ergebnis zurueck this.gameResult.set(null); this.errorMessage.set(''); // Setze Spielstatus this.gameInProgress.set(true); this.isActionInProgress.set(true); // Spiele Einsatz-Sound this.audioService.playBetSound(); // Erstelle Einsatz-Anfrage const request: CoinflipRequest = { betAmount: this.currentBet(), coinSide: side, }; // API aufrufen this.http .post('/backend/coinflip', request) .pipe( catchError((error) => { console.error('Fehler beim Spielen von Coinflip:', error); if (error.status === 400 && error.error.message.includes('insufficient')) { this.errorMessage.set('Unzureichendes Guthaben'); } else { this.errorMessage.set('Ein Fehler ist aufgetreten. Bitte versuche es erneut.'); } this.gameInProgress.set(false); return of(null); }), finalize(() => { this.isActionInProgress.set(false); }) ) .subscribe((result) => { if (!result) return; console.log('API-Antwort:', result); // Behebe moegliche Inkonsistenzen bei der Eigenschaftenbenennung vom Backend const fixedResult: CoinflipGame = { isWin: result.isWin ?? result.win, payout: result.payout, coinSide: result.coinSide, }; console.log('Korrigiertes Ergebnis:', fixedResult); // Spiele Muenzwurf-Animation und -Sound this.playCoinFlipAnimation(fixedResult.coinSide); // Setze Ergebnis nach Abschluss der Animation setTimeout(() => { this.gameResult.set(fixedResult); // Aktualisiere Guthaben mit neuem Wert vom Auth-Service this.authService.loadCurrentUser(); // Spiele Gewinn-Sound, wenn der Spieler gewonnen hat if (fixedResult.isWin) { this.audioService.playWinSound(); } // Setze Spielstatus nach Anzeigen des Ergebnisses zurueck setTimeout(() => { this.gameInProgress.set(false); }, 1500); }, 1100); // Kurz nach Ende der Animation }); } private playCoinFlipAnimation(result: 'HEAD' | 'TAILS') { if (!this.coinElement) return; const coinEl = this.coinElement.nativeElement; // Setze bestehende Animationen zurueck coinEl.classList.remove('animate-to-heads', 'animate-to-tails'); // Setze alle Inline-Styles von vorherigen Animationen zurueck coinEl.style.transform = ''; // Erzwinge Reflow, um Animation neu zu starten void coinEl.offsetWidth; // Spiele Muenzwurf-Sound if (this.coinflipSound) { this.coinflipSound.currentTime = 0; this.coinflipSound .play() .catch((err) => console.error('Fehler beim Abspielen des Sounds:', err)); } // Fuege passende Animationsklasse basierend auf dem Ergebnis hinzu if (result === 'HEAD') { coinEl.classList.add('animate-to-heads'); } else { coinEl.classList.add('animate-to-tails'); } console.log(`Animation angewendet fuer Ergebnis: ${result}`); } /** * Validiert Eingabe waehrend der Benutzer tippt, um ungueltige Werte zu verhindern */ validateBetInput(event: KeyboardEvent) { // Erlaube Navigationstasten (Pfeile, Entf, Ruecktaste, Tab) const navigationKeys = ['ArrowLeft', 'ArrowRight', 'Delete', 'Backspace', 'Tab']; if (navigationKeys.includes(event.key)) { return; } // Erlaube nur Zahlen if (!/^\d$/.test(event.key)) { event.preventDefault(); return; } // Ermittle den Wert, der nach dem Tastendruck entstehen wuerde const input = event.target as HTMLInputElement; const currentValue = input.value; const cursorPosition = input.selectionStart || 0; const newValue = currentValue.substring(0, cursorPosition) + event.key + currentValue.substring(input.selectionEnd || cursorPosition); const numValue = Number(newValue); // Verhindere Werte, die groesser als das Guthaben sind if (numValue > this.balance()) { event.preventDefault(); } } getResultClass() { if (!this.gameResult()) return ''; const result = this.gameResult(); const isWinner = result?.isWin || result?.win; return isWinner ? 'text-emerald-500' : 'text-accent-red'; } }