From 99f9f8d3c3fdff213c6464aea714c2161298bc67 Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 26 Mar 2025 15:30:55 +0100 Subject: [PATCH] feat(blackjack): implement game start and controls functionality --- .../casino/blackjack/BlackJackService.java | 1 + .../game/blackjack/blackjack.component.html | 21 ++-- .../game/blackjack/blackjack.component.ts | 75 ++++++++--- .../dealer-hand/dealer-hand.component.ts | 21 ++-- .../game-info/game-info.component.ts | 117 +++++++++++++++++- .../player-hand/player-hand.component.ts | 21 ++-- .../playing-card/playing-card.component.ts | 51 ++++++-- .../game/blackjack/models/blackjack.model.ts | 23 ++++ .../blackjack/services/blackjack.service.ts | 44 +++++++ 9 files changed, 320 insertions(+), 54 deletions(-) create mode 100644 frontend/src/app/feature/game/blackjack/models/blackjack.model.ts create mode 100644 frontend/src/app/feature/game/blackjack/services/blackjack.service.ts diff --git a/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java b/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java index ba5e7d1..cca78e4 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java @@ -23,6 +23,7 @@ public class BlackJackService { BlackJackGameEntity game = new BlackJackGameEntity(); game.setUser(user); game.setBet(betAmount); + game.setState("IN_PROGRESS"); for (int i = 0; i < 2; i++) { CardEntity playerCard = createRandomCard(game); diff --git a/frontend/src/app/feature/game/blackjack/blackjack.component.html b/frontend/src/app/feature/game/blackjack/blackjack.component.html index db4d698..ae130d4 100644 --- a/frontend/src/app/feature/game/blackjack/blackjack.component.html +++ b/frontend/src/app/feature/game/blackjack/blackjack.component.html @@ -3,20 +3,23 @@
- - - + + + @if (gameInProgress()) { + + }
diff --git a/frontend/src/app/feature/game/blackjack/blackjack.component.ts b/frontend/src/app/feature/game/blackjack/blackjack.component.ts index 4e66587..174c358 100644 --- a/frontend/src/app/feature/game/blackjack/blackjack.component.ts +++ b/frontend/src/app/feature/game/blackjack/blackjack.component.ts @@ -8,7 +8,8 @@ import { DealerHandComponent } from './components/dealer-hand/dealer-hand.compon import { PlayerHandComponent } from './components/player-hand/player-hand.component'; import { GameControlsComponent } from './components/game-controls/game-controls.component'; import { GameInfoComponent } from './components/game-info/game-info.component'; -import { Card } from './models/card.model'; +import { Card, BlackjackGame } from './models/blackjack.model'; +import { BlackjackService } from './services/blackjack.service'; @Component({ selector: 'app-blackjack', @@ -28,16 +29,14 @@ import { Card } from './models/card.model'; export default class BlackjackComponent { private router = inject(Router); private userService = inject(UserService); + private blackjackService = inject(BlackjackService); - dealerCards: Card[] = [ - { value: '2', suit: '♥', hidden: false }, - { value: '3', suit: '♦', hidden: false }, - { value: 'B', suit: '□', hidden: true }, - ]; - - playerCards: Card[] = []; - currentBet = 0; + dealerCards = signal([]); + playerCards = signal([]); + currentBet = signal(0); balance = signal(0); + currentGameId = signal(undefined); + gameInProgress = signal(false); constructor() { this.userService.getCurrentUser().subscribe((user) => { @@ -45,19 +44,65 @@ export default class BlackjackComponent { }); } + private updateGameState(game: BlackjackGame) { + console.log('Game state update:', game); + this.currentGameId.set(game.id); + this.currentBet.set(game.bet); + this.gameInProgress.set(game.state === 'IN_PROGRESS'); + + this.dealerCards.set( + game.dealerCards.map((card, index) => ({ + ...card, + hidden: index === 1 && game.state === 'IN_PROGRESS', + })) + ); + + this.playerCards.set( + game.playerCards.map((card) => ({ + ...card, + hidden: false, + })) + ); + } + + onNewGame(bet: number): void { + this.blackjackService.startGame(bet).subscribe({ + next: (game) => { + this.updateGameState(game); + }, + error: (error) => { + console.error('Failed to start game:', error); + }, + }); + } + onHit(): void { - // Implementation for hit action + if (!this.currentGameId()) return; + + this.blackjackService.hit(this.currentGameId()!).subscribe({ + next: (game) => { + this.updateGameState(game); + }, + error: (error) => { + console.error('Failed to hit:', error); + }, + }); } onStand(): void { - // Implementation for stand action + if (!this.currentGameId()) return; + + this.blackjackService.stand(this.currentGameId()!).subscribe({ + next: (game) => { + this.updateGameState(game); + }, + error: (error) => { + console.error('Failed to stand:', error); + }, + }); } leaveGame(): void { this.router.navigate(['/home']); } - - onNewGame(): void { - // Implementation for new game - } } diff --git a/frontend/src/app/feature/game/blackjack/components/dealer-hand/dealer-hand.component.ts b/frontend/src/app/feature/game/blackjack/components/dealer-hand/dealer-hand.component.ts index 6b8cdce..89517a1 100644 --- a/frontend/src/app/feature/game/blackjack/components/dealer-hand/dealer-hand.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/dealer-hand/dealer-hand.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { CommonModule } from '@angular/common'; import { PlayingCardComponent } from '../playing-card/playing-card.component'; -import { Card } from '../../models/card.model'; +import { Card } from '../../models/blackjack.model'; @Component({ selector: 'app-dealer-hand', @@ -12,12 +12,19 @@ import { Card } from '../../models/card.model';

Croupier's Karten

- + @if (cards.length > 0) { + @for (card of cards; track card) { + + } + } @else { +
+ Warte auf Spielstart... +
+ }
diff --git a/frontend/src/app/feature/game/blackjack/components/game-info/game-info.component.ts b/frontend/src/app/feature/game/blackjack/components/game-info/game-info.component.ts index 8f9d532..e35bf73 100644 --- a/frontend/src/app/feature/game/blackjack/components/game-info/game-info.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/game-info/game-info.component.ts @@ -1,10 +1,19 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + OnChanges, + Output, + SimpleChanges, +} from '@angular/core'; import { CommonModule, CurrencyPipe } from '@angular/common'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; @Component({ selector: 'app-game-info', standalone: true, - imports: [CommonModule, CurrencyPipe], + imports: [CommonModule, CurrencyPipe, ReactiveFormsModule], template: `

Spiel Informationen

@@ -19,14 +28,112 @@ import { CommonModule, CurrencyPipe } from '@angular/common'; {{ currentBet | currency: 'EUR' }}
- + + @if (!gameInProgress) { +
+ + + + +
+ } + +
+
+ + + @if (betForm.get('bet')?.errors?.['required'] && betForm.get('bet')?.touched) { + Bitte geben Sie einen Einsatz ein + } + @if (betForm.get('bet')?.errors?.['min'] && betForm.get('bet')?.touched) { + Mindestens 1€ setzen + } + @if (betForm.get('bet')?.errors?.['max'] && betForm.get('bet')?.touched) { + Nicht genügend Guthaben + } +
+ +
`, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class GameInfoComponent { +export class GameInfoComponent implements OnChanges { @Input() balance = 0; @Input() currentBet = 0; - @Output() newGame = new EventEmitter(); + @Input() gameInProgress = false; + @Output() newGame = new EventEmitter(); + + betForm: FormGroup; + + constructor(private fb: FormBuilder) { + this.betForm = this.fb.group({ + bet: ['', [Validators.required, Validators.min(1)]], + }); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes['balance']) { + this.betForm + .get('bet') + ?.setValidators([Validators.required, Validators.min(1), Validators.max(this.balance)]); + this.betForm.get('bet')?.updateValueAndValidity(); + } + } + + setBetAmount(percentage: number) { + const betAmount = Math.floor(this.balance * percentage * 100) / 100; + if (betAmount >= 1) { + this.betForm.patchValue({ bet: betAmount }); + } + } + + onSubmit() { + if (this.betForm.valid) { + const betAmount = parseFloat(this.betForm.value.bet); + if (betAmount <= this.balance) { + this.newGame.emit(betAmount); + this.betForm.reset(); + } + } + } } diff --git a/frontend/src/app/feature/game/blackjack/components/player-hand/player-hand.component.ts b/frontend/src/app/feature/game/blackjack/components/player-hand/player-hand.component.ts index 2dc50c0..e158f67 100644 --- a/frontend/src/app/feature/game/blackjack/components/player-hand/player-hand.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/player-hand/player-hand.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { CommonModule } from '@angular/common'; import { PlayingCardComponent } from '../playing-card/playing-card.component'; -import { Card } from '../../models/card.model'; +import { Card } from '../../models/blackjack.model'; @Component({ selector: 'app-player-hand', @@ -14,12 +14,19 @@ import { Card } from '../../models/card.model';
- + @if (cards.length > 0) { + @for (card of cards; track card) { + + } + } @else { +
+ Platziere eine Wette um zu spielen... +
+ }
diff --git a/frontend/src/app/feature/game/blackjack/components/playing-card/playing-card.component.ts b/frontend/src/app/feature/game/blackjack/components/playing-card/playing-card.component.ts index dc93df8..186ac9b 100644 --- a/frontend/src/app/feature/game/blackjack/components/playing-card/playing-card.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/playing-card/playing-card.component.ts @@ -1,5 +1,6 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { suitSymbols, Suit } from '../../models/blackjack.model'; @Component({ selector: 'app-playing-card', @@ -10,21 +11,49 @@ import { CommonModule } from '@angular/common'; class="w-24 h-36 rounded-lg p-2 relative flex flex-col justify-between shadow-lg" [class]="hidden ? 'bg-red-800' : 'bg-white'" > - {{ value }} - {{ suit }} - {{ - value - }} + @if (!hidden) { + {{ getDisplayRank(rank) }} + } + @if (!hidden) { + {{ getSuitSymbol(suit) }} + } + @if (!hidden) { + {{ + getDisplayRank(rank) + }} + } `, changeDetection: ChangeDetectionStrategy.OnPush, }) export class PlayingCardComponent { - @Input({ required: true }) value!: string; - @Input({ required: true }) suit!: string; + @Input({ required: true }) rank!: string; + @Input({ required: true }) suit!: Suit; @Input({ required: true }) hidden!: boolean; + + protected getSuitSymbol(suit: Suit): string { + return suitSymbols[suit]; + } + + protected getDisplayRank(rank: string): string { + const rankMap: Record = { + TWO: '2', + THREE: '3', + FOUR: '4', + FIVE: '5', + SIX: '6', + SEVEN: '7', + EIGHT: '8', + NINE: '9', + TEN: '10', + JACK: 'J', + QUEEN: 'Q', + KING: 'K', + ACE: 'A', + }; + return rankMap[rank] || rank; + } } diff --git a/frontend/src/app/feature/game/blackjack/models/blackjack.model.ts b/frontend/src/app/feature/game/blackjack/models/blackjack.model.ts new file mode 100644 index 0000000..55f2ee1 --- /dev/null +++ b/frontend/src/app/feature/game/blackjack/models/blackjack.model.ts @@ -0,0 +1,23 @@ +export type Suit = 'HEARTS' | 'DIAMONDS' | 'CLUBS' | 'SPADES'; + +export interface Card { + suit: Suit; + rank: string; + hidden: boolean; +} + +export interface BlackjackGame { + id: number; + state: string; + bet: number; + playerCards: Card[]; + dealerCards: Card[]; + userId: number; +} + +export const suitSymbols: Record = { + HEARTS: '♥', + DIAMONDS: '♦', + CLUBS: '♣', + SPADES: '♠', +}; diff --git a/frontend/src/app/feature/game/blackjack/services/blackjack.service.ts b/frontend/src/app/feature/game/blackjack/services/blackjack.service.ts new file mode 100644 index 0000000..86ab7cf --- /dev/null +++ b/frontend/src/app/feature/game/blackjack/services/blackjack.service.ts @@ -0,0 +1,44 @@ +import { Injectable, inject } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, catchError } from 'rxjs'; +import { BlackjackGame } from '../models/blackjack.model'; + +@Injectable({ + providedIn: 'root', +}) +export class BlackjackService { + private http = inject(HttpClient); + + startGame(bet: number): Observable { + return this.http + .post('/backend/blackjack/start', { betAmount: bet }, { responseType: 'json' }) + .pipe( + catchError((error) => { + console.error('Start game error:', error); + throw error; + }) + ); + } + + hit(gameId: number): Observable { + return this.http + .post(`/backend/blackjack/${gameId}/hit`, {}, { responseType: 'json' }) + .pipe( + catchError((error) => { + console.error('Hit error:', error); + throw error; + }) + ); + } + + stand(gameId: number): Observable { + return this.http + .post(`/backend/blackjack/${gameId}/stand`, {}, { responseType: 'json' }) + .pipe( + catchError((error) => { + console.error('Stand error:', error); + throw error; + }) + ); + } +} -- 2.45.3