diff --git a/frontend/public/coinflip.png b/frontend/public/coinflip.png deleted file mode 100644 index 0f39ca8..0000000 Binary files a/frontend/public/coinflip.png and /dev/null differ diff --git a/frontend/public/poker.webp b/frontend/public/poker.webp new file mode 100644 index 0000000..329f4da Binary files /dev/null and b/frontend/public/poker.webp differ diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 6e9f9b7..b8adc5f 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -45,11 +45,6 @@ export const routes: Routes = [ loadComponent: () => import('./feature/game/blackjack/blackjack.component'), canActivate: [authGuard], }, - { - path: 'game/coinflip', - loadComponent: () => import('./feature/game/coinflip/coinflip.component'), - canActivate: [authGuard], - }, { path: 'game/slots', loadComponent: () => import('./feature/game/slots/slots.component'), diff --git a/frontend/src/app/feature/game/coinflip/coinflip.component.css b/frontend/src/app/feature/game/coinflip/coinflip.component.css deleted file mode 100644 index 06c9671..0000000 --- a/frontend/src/app/feature/game/coinflip/coinflip.component.css +++ /dev/null @@ -1,117 +0,0 @@ -/* Custom CSS for 3D Transformations and Coin Flip */ -@keyframes flipToHeads { - 0% { - transform: rotateY(0); - } - 100% { - transform: rotateY(1800deg); /* End with heads facing up (even number of Y rotations) */ - } -} - -@keyframes flipToTails { - 0% { - transform: rotateY(0); - } - 100% { - transform: rotateY(1980deg); /* End with tails facing up (odd number of Y rotations) */ - } -} - -.coin-container { - width: 180px; - height: 180px; - perspective: 1000px; - margin: 20px auto; -} - -.coin { - width: 100%; - height: 100%; - position: relative; - transform-style: preserve-3d; - transition: transform 0.01s; - transform: rotateY(0deg); - box-shadow: 0 0 30px rgba(0, 0, 0, 0.4); -} - -.coin-side { - width: 100%; - height: 100%; - position: absolute; - backface-visibility: hidden; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-weight: bold; - font-size: 24px; - box-shadow: inset 0 0 15px rgba(0, 0, 0, 0.2); - border: 8px solid rgba(255, 255, 255, 0.2); -} - -.front { - background: linear-gradient(45deg, #ffd700, #ffb700); - color: #333; - z-index: 2; - transform: rotateY(0); -} - -.back { - background: linear-gradient(45deg, #5a5a5a, #333333); - color: white; - z-index: 1; - transform: rotateY(180deg); -} - -/* We apply transform directly to the SVG element in HTML */ - -/* Text for both sides */ -.coin-text { - /* Ensure text is readable */ - user-select: none; - pointer-events: none; -} - -/* Animation classes */ -.coin.animate-to-heads { - animation: flipToHeads 1s ease-in-out forwards; -} - -.coin.animate-to-tails { - animation: flipToTails 1s ease-in-out forwards; -} - -/* Make the buttons more responsive */ -button:not([disabled]) { - cursor: pointer; - transition: all 0.2s ease; -} - -button:not([disabled]):hover { - transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); -} - -button:not([disabled]):active { - transform: translateY(1px); -} - -/* Animation for results */ -@keyframes popIn { - 0% { - transform: scale(0.8); - opacity: 0; - } - 70% { - transform: scale(1.1); - opacity: 1; - } - 100% { - transform: scale(1); - opacity: 1; - } -} - -.result-text { - animation: popIn 0.5s ease-out forwards; -} diff --git a/frontend/src/app/feature/game/coinflip/coinflip.component.html b/frontend/src/app/feature/game/coinflip/coinflip.component.html deleted file mode 100644 index 671bc31..0000000 --- a/frontend/src/app/feature/game/coinflip/coinflip.component.html +++ /dev/null @@ -1,143 +0,0 @@ -
-
-
- - @if (gameResult()) { -
-

- {{ gameResult()?.isWin ? 'You Won!' : 'You Lost' }} -

-

- Coin landed on: - {{ - gameResult()?.coinSide === 'HEAD' ? 'HEAD' : 'TAILS' - }} -

- @if (gameResult()?.isWin) { -

- +{{ gameResult()?.payout | currency: 'EUR' }} -

- } -
- } - - - @if (errorMessage()) { -
-

{{ errorMessage() }}

-
- } - - -
-
- -
-
HEAD
-
- - -
- - TAILS -
-
-
- - -
- - -
-
- - -
-
-

Game Information

-
- -
- Current Bet: - - € - -
- - -
- Your Balance: - - {{ balance() | currency: 'EUR' }} - -
- - - @if (!gameInProgress()) { -
- - - - -
- } - - -
-
- - Cannot exceed balance -
- -
- - -
-

How to Play

-
    -
  • • Choose your bet amount
  • -
  • • Select Heads or Tails
  • -
  • • Win double your bet if correct
  • -
-
-
-
-
-
-
diff --git a/frontend/src/app/feature/game/coinflip/coinflip.component.ts b/frontend/src/app/feature/game/coinflip/coinflip.component.ts deleted file mode 100644 index 1766e4d..0000000 --- a/frontend/src/app/feature/game/coinflip/coinflip.component.ts +++ /dev/null @@ -1,248 +0,0 @@ -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 { - // Subscribe to user updates for real-time balance changes - this.authService.userSubject.subscribe((user) => { - if (user) { - this.balance.set(user.balance); - } - }); - - // Initialize coinflip 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); - - // Reset invalid bet state - this.isInvalidBet.set(false); - - // Enforce minimum bet of 1 - if (value <= 0) { - value = 1; - } - - // Cap bet at available balance and show feedback - if (value > this.balance()) { - value = this.balance(); - // Show visual feedback - this.isInvalidBet.set(true); - // Indicate the error briefly - setTimeout(() => this.isInvalidBet.set(false), 800); - // Update the input field directly to show the user the max value - inputElement.value = String(value); - } - - // Update signals - 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; - - // Reset previous result - this.gameResult.set(null); - this.errorMessage.set(''); - - // Set game state - this.gameInProgress.set(true); - this.isActionInProgress.set(true); - - // Play bet sound - this.audioService.playBetSound(); - - // Create bet request - const request: CoinflipRequest = { - betAmount: this.currentBet(), - coinSide: side, - }; - - // Call API - this.http - .post('/backend/coinflip', request) - .pipe( - catchError((error) => { - console.error('Error playing coinflip:', error); - - if (error.status === 400 && error.error.message.includes('insufficient')) { - this.errorMessage.set('Insufficient funds'); - } else { - this.errorMessage.set('An error occurred. Please try again.'); - } - - this.gameInProgress.set(false); - return of(null); - }), - finalize(() => { - this.isActionInProgress.set(false); - }) - ) - .subscribe((result) => { - if (!result) return; - - console.log('API response:', result); - - // Fix potential property naming inconsistency from the backend - const fixedResult: CoinflipGame = { - isWin: result.isWin ?? result.win, - payout: result.payout, - coinSide: result.coinSide, - }; - - console.log('Fixed result:', fixedResult); - - // Play coin flip animation and sound - this.playCoinFlipAnimation(fixedResult.coinSide); - - // Set result after animation completes - setTimeout(() => { - this.gameResult.set(fixedResult); - - // Update balance with new value from auth service - this.authService.loadCurrentUser(); - - // Play win sound if player won - if (fixedResult.isWin) { - this.audioService.playWinSound(); - } - - // Reset game state after showing result - setTimeout(() => { - this.gameInProgress.set(false); - }, 1500); - }, 1100); // Just after animation ends - }); - } - - private playCoinFlipAnimation(result: 'HEAD' | 'TAILS') { - if (!this.coinElement) return; - - const coinEl = this.coinElement.nativeElement; - - // Reset any existing animations - coinEl.classList.remove('animate-to-heads', 'animate-to-tails'); - - // Reset any inline styles from previous animations - coinEl.style.transform = ''; - - // Force a reflow to restart animation - void coinEl.offsetWidth; - - // Play flip sound - if (this.coinflipSound) { - this.coinflipSound.currentTime = 0; - this.coinflipSound.play().catch((err) => console.error('Error playing sound:', err)); - } - - // Add appropriate animation class based on result - if (result === 'HEAD') { - coinEl.classList.add('animate-to-heads'); - } else { - coinEl.classList.add('animate-to-tails'); - } - - console.log(`Animation applied for result: ${result}`); - } - - /** - * Validates input as the user types to prevent invalid values - */ - validateBetInput(event: KeyboardEvent) { - // Allow navigation keys (arrows, delete, backspace, tab) - const navigationKeys = ['ArrowLeft', 'ArrowRight', 'Delete', 'Backspace', 'Tab']; - if (navigationKeys.includes(event.key)) { - return; - } - - // Only allow numbers - if (!/^\d$/.test(event.key)) { - event.preventDefault(); - return; - } - - // Get the value that would result after the keypress - 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); - - // Prevent values greater than balance - if (numValue > this.balance()) { - event.preventDefault(); - } - } - - // We removed the paste handler for simplicity since the updateBet method - // will handle any value that gets into the input field - - getResultClass() { - if (!this.gameResult()) return ''; - const result = this.gameResult(); - const isWinner = result?.isWin || result?.win; - return isWinner ? 'text-emerald-500' : 'text-accent-red'; - } -} diff --git a/frontend/src/app/feature/game/coinflip/models/coinflip.model.ts b/frontend/src/app/feature/game/coinflip/models/coinflip.model.ts deleted file mode 100644 index f87a3f9..0000000 --- a/frontend/src/app/feature/game/coinflip/models/coinflip.model.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface CoinflipGame { - isWin?: boolean; - win?: boolean; - payout: number; - coinSide: 'HEAD' | 'TAILS'; -} - -export interface CoinflipRequest { - betAmount: number; - coinSide: 'HEAD' | 'TAILS'; -} diff --git a/frontend/src/app/feature/home/home.component.ts b/frontend/src/app/feature/home/home.component.ts index 1fad4cb..494c03f 100644 --- a/frontend/src/app/feature/home/home.component.ts +++ b/frontend/src/app/feature/home/home.component.ts @@ -48,9 +48,9 @@ export default class HomeComponent implements OnInit { featuredGames: Game[] = [ { id: '1', - name: 'Coinflip', - image: '/coinflip.png', - route: '/game/coinflip', + name: 'Poker', + image: '/poker.webp', + route: '/game/poker', }, { id: '2', diff --git a/justfile b/justfile index 4fc777c..1d68253 100644 --- a/justfile +++ b/justfile @@ -21,7 +21,3 @@ build-be: # Builds the frontend docker image build-fe: docker buildx build -f frontend/.docker/Dockerfile -t git.kjan.de/szut/casino-frontend:latest frontend - -# Formats the code duh -format: - cd frontend && bunx prettier --write "src/**/*.{ts,html,css,scss}"