From 3ef5530e77fd046affdb36110dfca7585ae8c614 Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 2 Apr 2025 11:25:03 +0200 Subject: [PATCH 1/3] feat(blackjack): add split functionality to the game --- .../blackjack/BlackJackGameController.java | 17 + .../casino/blackjack/BlackJackGameEntity.java | 11 + .../casino/blackjack/BlackJackService.java | 563 ++++++++++-------- .../de/szut/casino/blackjack/CardEntity.java | 2 +- .../game/blackjack/blackjack.component.ts | 32 +- .../game-info/game-info.component.ts | 30 +- .../blackjack/services/betting.service.ts | 31 + 7 files changed, 410 insertions(+), 276 deletions(-) create mode 100644 frontend/src/app/feature/game/blackjack/services/betting.service.ts diff --git a/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameController.java b/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameController.java index a10fe63..c89f3ef 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameController.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameController.java @@ -94,6 +94,23 @@ public class BlackJackGameController { return ResponseEntity.ok(blackJackService.doubleDown(game)); } + @PostMapping("/blackjack/{id}/split") + public ResponseEntity split(@PathVariable Long id, @RequestHeader("Authorization") String token) { + Optional optionalUser = userService.getCurrentUser(token); + + if (optionalUser.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + UserEntity user = optionalUser.get(); + BlackJackGameEntity game = blackJackService.getBlackJackGame(id); + if (game == null || !Objects.equals(game.getUserId(), user.getId())) { + return ResponseEntity.notFound().build(); + } + + return ResponseEntity.ok(blackJackService.split(game)); + } + @PostMapping("/blackjack/start") public ResponseEntity createBlackJackGame(@RequestBody @Valid CreateBlackJackGameDto createBlackJackGameDto, @RequestHeader("Authorization") String token) { Optional optionalUser = userService.getCurrentUser(token); diff --git a/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameEntity.java b/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameEntity.java index 4f22c9d..4b03b21 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameEntity.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameEntity.java @@ -51,4 +51,15 @@ public class BlackJackGameEntity { @JsonManagedReference @SQLRestriction("card_type = 'DEALER'") private List dealerCards = new ArrayList<>(); + + @OneToMany(mappedBy = "game", cascade = CascadeType.ALL, orphanRemoval = true) + @JsonManagedReference + @SQLRestriction("card_type = 'PLAYER_SPLIT'") + private List playerSplitCards = new ArrayList<>(); + + @Column(name = "split_bet") + private BigDecimal splitBet; + + @Column(name = "is_split") + private boolean isSplit; } 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 2ce7b07..a37a788 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java @@ -1,245 +1,318 @@ -package de.szut.casino.blackjack; - -import de.szut.casino.user.UserEntity; -import de.szut.casino.user.UserRepository; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.math.BigDecimal; -import java.util.List; -import java.util.Optional; -import java.util.Random; - -@Service -public class BlackJackService { - private final BlackJackGameRepository blackJackGameRepository; - private final UserRepository userRepository; - private final Random random = new Random(); - - public BlackJackService(BlackJackGameRepository blackJackGameRepository, UserRepository userRepository) { - this.blackJackGameRepository = blackJackGameRepository; - this.userRepository = userRepository; - } - - public BlackJackGameEntity getBlackJackGame(Long id) { - return blackJackGameRepository.findById(id).orElse(null); - } - - @Transactional - public BlackJackGameEntity createBlackJackGame(UserEntity user, BigDecimal betAmount) { - BlackJackGameEntity game = new BlackJackGameEntity(); - game.setUser(user); - game.setBet(betAmount); - - initializeDeck(game); - dealInitialCards(game); - - game.setState(getState(game)); - deductBetFromBalance(user, betAmount); - - return blackJackGameRepository.save(game); - } - - @Transactional - public BlackJackGameEntity hit(BlackJackGameEntity game) { - game = refreshGameState(game); - - if (game.getState() != BlackJackState.IN_PROGRESS) { - return game; - } - - dealCardToPlayer(game); - updateGameStateAndBalance(game); - - return blackJackGameRepository.save(game); - } - - @Transactional - public BlackJackGameEntity stand(BlackJackGameEntity game) { - game = refreshGameState(game); - - if (game.getState() != BlackJackState.IN_PROGRESS) { - return game; - } - - dealCardsToDealerUntilMinimumScore(game); - determineWinnerAndUpdateBalance(game); - - return blackJackGameRepository.save(game); - } - - @Transactional - public BlackJackGameEntity doubleDown(BlackJackGameEntity game) { - game = refreshGameState(game); - - if (game.getState() != BlackJackState.IN_PROGRESS || game.getPlayerCards().size() != 2) { - return game; - } - - UserEntity user = getUserWithFreshData(game.getUser()); - BigDecimal additionalBet = game.getBet(); - - if (user.getBalance().compareTo(additionalBet) < 0) { - return game; - } - - deductBetFromBalance(user, additionalBet); - game.setBet(game.getBet().add(additionalBet)); - - dealCardToPlayer(game); - updateGameStateAndBalance(game); - - if (game.getState() == BlackJackState.IN_PROGRESS) { - return stand(game); - } - - return game; - } - - private BlackJackGameEntity refreshGameState(BlackJackGameEntity game) { - return blackJackGameRepository.findById(game.getId()).orElse(game); - } - - private UserEntity getUserWithFreshData(UserEntity user) { - return userRepository.findById(user.getId()).orElse(user); - } - - private void dealInitialCards(BlackJackGameEntity game) { - for (int i = 0; i < 2; i++) { - dealCardToPlayer(game); - } - - dealCardToDealer(game); - } - - private void dealCardToPlayer(BlackJackGameEntity game) { - CardEntity card = drawCardFromDeck(game); - card.setCardType(CardType.PLAYER); - game.getPlayerCards().add(card); - } - - private void dealCardToDealer(BlackJackGameEntity game) { - CardEntity card = drawCardFromDeck(game); - card.setCardType(CardType.DEALER); - game.getDealerCards().add(card); - } - - private void dealCardsToDealerUntilMinimumScore(BlackJackGameEntity game) { - while (calculateHandValue(game.getDealerCards()) < 17) { - dealCardToDealer(game); - } - } - - private void updateGameStateAndBalance(BlackJackGameEntity game) { - game.setState(getState(game)); - - if (game.getState() == BlackJackState.PLAYER_WON) { - updateUserBalance(game, true); - } else if (game.getState() == BlackJackState.PLAYER_LOST) { - updateUserBalance(game, false); - } - } - - private void determineWinnerAndUpdateBalance(BlackJackGameEntity game) { - int playerValue = calculateHandValue(game.getPlayerCards()); - int dealerValue = calculateHandValue(game.getDealerCards()); - - if (dealerValue > 21 || playerValue > dealerValue) { - game.setState(BlackJackState.PLAYER_WON); - updateUserBalance(game, true); - } else if (playerValue < dealerValue) { - game.setState(BlackJackState.PLAYER_LOST); - updateUserBalance(game, false); - } else { - game.setState(BlackJackState.DRAW); - updateUserBalance(game, false); // For draw, player gets their bet back - } - } - - private void deductBetFromBalance(UserEntity user, BigDecimal betAmount) { - user.setBalance(user.getBalance().subtract(betAmount)); - userRepository.save(user); - } - - @Transactional - private void updateUserBalance(BlackJackGameEntity game, boolean isWin) { - UserEntity user = getUserWithFreshData(game.getUser()); - BigDecimal betAmount = game.getBet(); - BigDecimal balance = user.getBalance(); - - if (isWin) { - balance = balance.add(betAmount.multiply(BigDecimal.valueOf(2))); - } else if (game.getState() == BlackJackState.DRAW) { - balance = balance.add(betAmount); - } - - user.setBalance(balance); - userRepository.save(user); - } - - private void initializeDeck(BlackJackGameEntity game) { - for (Suit suit : Suit.values()) { - for (Rank rank : Rank.values()) { - CardEntity card = new CardEntity(); - card.setGame(game); - card.setSuit(suit); - card.setRank(rank); - card.setCardType(CardType.DECK); - game.getDeck().add(card); - } - } - - java.util.Collections.shuffle(game.getDeck(), random); - } - - private CardEntity drawCardFromDeck(BlackJackGameEntity game) { - if (game.getDeck().isEmpty()) { - throw new IllegalStateException("Deck is empty"); - } - - return game.getDeck().removeFirst(); - } - - private BlackJackState getState(BlackJackGameEntity game) { - int playerHandValue = calculateHandValue(game.getPlayerCards()); - - if (playerHandValue == 21) { - CardEntity hole = drawCardFromDeck(game); - hole.setCardType(CardType.DEALER); - game.getDealerCards().add(hole); - - int dealerHandValue = calculateHandValue(game.getDealerCards()); - - if (dealerHandValue == 21) { - return BlackJackState.DRAW; - } else { - BigDecimal blackjackWinnings = game.getBet().multiply(new BigDecimal("1.5")); - UserEntity user = getUserWithFreshData(game.getUser()); - user.setBalance(user.getBalance().add(blackjackWinnings)); - return BlackJackState.PLAYER_BLACKJACK; - } - } else if (playerHandValue > 21) { - return BlackJackState.PLAYER_LOST; - } - - return BlackJackState.IN_PROGRESS; - } - - private int calculateHandValue(List hand) { - int sum = 0; - int aceCount = 0; - for (CardEntity card : hand) { - sum += card.getRank().getValue(); - if (card.getRank() == Rank.ACE) { - aceCount++; - } - } - - while (sum > 21 && aceCount > 0) { - sum -= 10; - aceCount--; - } - - return sum; - } -} +package de.szut.casino.blackjack; + +import de.szut.casino.user.UserEntity; +import de.szut.casino.user.UserRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; +import java.util.Random; + +@Service +public class BlackJackService { + private final BlackJackGameRepository blackJackGameRepository; + private final UserRepository userRepository; + private final Random random = new Random(); + + public BlackJackService(BlackJackGameRepository blackJackGameRepository, UserRepository userRepository) { + this.blackJackGameRepository = blackJackGameRepository; + this.userRepository = userRepository; + } + + public BlackJackGameEntity getBlackJackGame(Long id) { + return blackJackGameRepository.findById(id).orElse(null); + } + + @Transactional + public BlackJackGameEntity createBlackJackGame(UserEntity user, BigDecimal betAmount) { + BlackJackGameEntity game = new BlackJackGameEntity(); + game.setUser(user); + game.setBet(betAmount); + + initializeDeck(game); + dealInitialCards(game); + + game.setState(getState(game)); + deductBetFromBalance(user, betAmount); + + return blackJackGameRepository.save(game); + } + + @Transactional + public BlackJackGameEntity hit(BlackJackGameEntity game) { + if (game.getState() != BlackJackState.IN_PROGRESS) { + return game; + } + + if (game.isSplit()) { + dealCardToPlayer(game); + } else { + dealCardToPlayer(game); + } + + updateGameStateAndBalance(game); + + return blackJackGameRepository.save(game); + } + + @Transactional + public BlackJackGameEntity stand(BlackJackGameEntity game) { + if (game.getState() != BlackJackState.IN_PROGRESS) { + return game; + } + + dealCardsToDealerUntilMinimumScore(game); + determineWinnerAndUpdateBalance(game); + + return blackJackGameRepository.save(game); + } + + @Transactional + public BlackJackGameEntity doubleDown(BlackJackGameEntity game) { + if (game.getState() != BlackJackState.IN_PROGRESS || game.getPlayerCards().size() != 2) { + return game; + } + + UserEntity user = getUserWithFreshData(game.getUser()); + BigDecimal additionalBet = game.getBet(); + + if (user.getBalance().compareTo(additionalBet) < 0) { + return game; + } + + deductBetFromBalance(user, additionalBet); + game.setBet(game.getBet().add(additionalBet)); + + dealCardToPlayer(game); + updateGameStateAndBalance(game); + + if (game.getState() == BlackJackState.IN_PROGRESS) { + return stand(game); + } + + return game; + } + + @Transactional + public BlackJackGameEntity split(BlackJackGameEntity game) { + if (game.getState() != BlackJackState.IN_PROGRESS || + game.getPlayerCards().size() != 2 || + game.isSplit() || + !game.getPlayerCards().get(0).getRank().equals(game.getPlayerCards().get(1).getRank())) { + return game; + } + + UserEntity user = getUserWithFreshData(game.getUser()); + BigDecimal splitBet = game.getBet(); + + if (user.getBalance().compareTo(splitBet) < 0) { + return game; + } + + deductBetFromBalance(user, splitBet); + game.setSplitBet(splitBet); + game.setSplit(true); + + CardEntity card = game.getPlayerCards().remove(1); + card.setCardType(CardType.PLAYER_SPLIT); + game.getPlayerSplitCards().add(card); + + dealCardToPlayer(game); + dealCardToSplitHand(game); + + return blackJackGameRepository.save(game); + } + + private BlackJackGameEntity refreshGameState(BlackJackGameEntity game) { + return blackJackGameRepository.findById(game.getId()).orElse(game); + } + + private UserEntity getUserWithFreshData(UserEntity user) { + return userRepository.findById(user.getId()).orElse(user); + } + + private void dealInitialCards(BlackJackGameEntity game) { + for (int i = 0; i < 2; i++) { + dealCardToPlayer(game); + } + + dealCardToDealer(game); + } + + private void dealCardToPlayer(BlackJackGameEntity game) { + CardEntity card = drawCardFromDeck(game); + card.setCardType(CardType.PLAYER); + game.getPlayerCards().add(card); + } + + private void dealCardToDealer(BlackJackGameEntity game) { + CardEntity card = drawCardFromDeck(game); + card.setCardType(CardType.DEALER); + game.getDealerCards().add(card); + } + + private void dealCardsToDealerUntilMinimumScore(BlackJackGameEntity game) { + while (calculateHandValue(game.getDealerCards()) < 17) { + dealCardToDealer(game); + } + } + + private void dealCardToSplitHand(BlackJackGameEntity game) { + CardEntity card = drawCardFromDeck(game); + card.setCardType(CardType.PLAYER_SPLIT); + game.getPlayerSplitCards().add(card); + } + + private void updateGameStateAndBalance(BlackJackGameEntity game) { + if (game.isSplit()) { + int mainHandValue = calculateHandValue(game.getPlayerCards()); + int splitHandValue = calculateHandValue(game.getPlayerSplitCards()); + + if (mainHandValue > 21 && splitHandValue > 21) { + game.setState(BlackJackState.PLAYER_LOST); + updateUserBalance(game, false); + } else if (mainHandValue <= 21 && splitHandValue <= 21) { + game.setState(BlackJackState.IN_PROGRESS); + } else { + game.setState(BlackJackState.IN_PROGRESS); + } + } else { + game.setState(getState(game)); + + if (game.getState() == BlackJackState.PLAYER_WON) { + updateUserBalance(game, true); + } else if (game.getState() == BlackJackState.PLAYER_LOST) { + updateUserBalance(game, false); + } + } + } + + private void determineWinnerAndUpdateBalance(BlackJackGameEntity game) { + int playerValue = calculateHandValue(game.getPlayerCards()); + int dealerValue = calculateHandValue(game.getDealerCards()); + + if (dealerValue > 21 || playerValue > dealerValue) { + game.setState(BlackJackState.PLAYER_WON); + updateUserBalance(game, true); + } else if (playerValue < dealerValue) { + game.setState(BlackJackState.PLAYER_LOST); + updateUserBalance(game, false); + } else { + game.setState(BlackJackState.DRAW); + updateUserBalance(game, false); + } + } + + private void deductBetFromBalance(UserEntity user, BigDecimal betAmount) { + user.setBalance(user.getBalance().subtract(betAmount)); + userRepository.save(user); + } + + @Transactional + private void updateUserBalance(BlackJackGameEntity game, boolean isWin) { + UserEntity user = getUserWithFreshData(game.getUser()); + BigDecimal totalBet = game.getBet(); + BigDecimal balance = user.getBalance(); + + if (game.isSplit()) { + totalBet = totalBet.add(game.getSplitBet()); + + if (isWin) { + int mainHandValue = calculateHandValue(game.getPlayerCards()); + int splitHandValue = calculateHandValue(game.getPlayerSplitCards()); + int dealerValue = calculateHandValue(game.getDealerCards()); + + if (mainHandValue <= 21 && (dealerValue > 21 || mainHandValue > dealerValue)) { + balance = balance.add(game.getBet().multiply(BigDecimal.valueOf(2))); + } else if (mainHandValue == dealerValue) { + balance = balance.add(game.getBet()); + } + + if (splitHandValue <= 21 && (dealerValue > 21 || splitHandValue > dealerValue)) { + balance = balance.add(game.getSplitBet().multiply(BigDecimal.valueOf(2))); + } else if (splitHandValue == dealerValue) { + balance = balance.add(game.getSplitBet()); + } + } else if (game.getState() == BlackJackState.DRAW) { + balance = balance.add(totalBet); + } + } else { + if (isWin) { + balance = balance.add(totalBet.multiply(BigDecimal.valueOf(2))); + } else if (game.getState() == BlackJackState.DRAW) { + balance = balance.add(totalBet); + } + } + + user.setBalance(balance); + userRepository.save(user); + } + + private void initializeDeck(BlackJackGameEntity game) { + for (Suit suit : Suit.values()) { + for (Rank rank : Rank.values()) { + CardEntity card = new CardEntity(); + card.setGame(game); + card.setSuit(suit); + card.setRank(rank); + card.setCardType(CardType.DECK); + game.getDeck().add(card); + } + } + + java.util.Collections.shuffle(game.getDeck(), random); + } + + private CardEntity drawCardFromDeck(BlackJackGameEntity game) { + if (game.getDeck().isEmpty()) { + throw new IllegalStateException("Deck is empty"); + } + + return game.getDeck().removeFirst(); + } + + private BlackJackState getState(BlackJackGameEntity game) { + int playerHandValue = calculateHandValue(game.getPlayerCards()); + + if (playerHandValue == 21) { + CardEntity hole = drawCardFromDeck(game); + hole.setCardType(CardType.DEALER); + game.getDealerCards().add(hole); + + int dealerHandValue = calculateHandValue(game.getDealerCards()); + + if (dealerHandValue == 21) { + return BlackJackState.DRAW; + } else { + BigDecimal blackjackWinnings = game.getBet().multiply(new BigDecimal("1.5")); + UserEntity user = getUserWithFreshData(game.getUser()); + user.setBalance(user.getBalance().add(blackjackWinnings)); + return BlackJackState.PLAYER_BLACKJACK; + } + } else if (playerHandValue > 21) { + return BlackJackState.PLAYER_LOST; + } + + return BlackJackState.IN_PROGRESS; + } + + private int calculateHandValue(List hand) { + int sum = 0; + int aceCount = 0; + for (CardEntity card : hand) { + sum += card.getRank().getValue(); + if (card.getRank() == Rank.ACE) { + aceCount++; + } + } + + while (sum > 21 && aceCount > 0) { + sum -= 10; + aceCount--; + } + + return sum; + } +} diff --git a/backend/src/main/java/de/szut/casino/blackjack/CardEntity.java b/backend/src/main/java/de/szut/casino/blackjack/CardEntity.java index 3b6903a..e2c1f0e 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/CardEntity.java +++ b/backend/src/main/java/de/szut/casino/blackjack/CardEntity.java @@ -36,5 +36,5 @@ public class CardEntity { } enum CardType { - DECK, PLAYER, DEALER + DECK, PLAYER, DEALER, PLAYER_SPLIT } diff --git a/frontend/src/app/feature/game/blackjack/blackjack.component.ts b/frontend/src/app/feature/game/blackjack/blackjack.component.ts index d6bfec4..78f3533 100644 --- a/frontend/src/app/feature/game/blackjack/blackjack.component.ts +++ b/frontend/src/app/feature/game/blackjack/blackjack.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, signal, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Router } from '@angular/router'; import { PlayingCardComponent } from './components/playing-card/playing-card.component'; @@ -13,6 +13,7 @@ import { GameResultComponent } from '@blackjack/components/game-result/game-resu import { GameState } from '@blackjack/enum/gameState'; import { NavbarComponent } from '@shared/components/navbar/navbar.component'; import { UserService } from '@service/user.service'; +import { timer } from 'rxjs'; @Component({ selector: 'app-blackjack', @@ -30,7 +31,7 @@ import { UserService } from '@service/user.service'; templateUrl: './blackjack.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export default class BlackjackComponent { +export default class BlackjackComponent implements OnInit { private router = inject(Router); private userService = inject(UserService); private blackjackService = inject(BlackjackService); @@ -47,13 +48,11 @@ export default class BlackjackComponent { isActionInProgress = signal(false); currentAction = signal(''); - constructor() { - this.refreshUserBalance(); - } - - private refreshUserBalance(): void { - this.userService.getCurrentUser().subscribe((user) => { - this.balance.set(user?.balance ?? 0); + ngOnInit(): void { + this.userService.currentUser$.subscribe((user) => { + if (user) { + this.balance.set(user.balance); + } }); } @@ -83,8 +82,10 @@ export default class BlackjackComponent { if (isGameOver) { console.log('Game is over, state:', game.state); this.userService.refreshCurrentUser(); - this.showGameResult.set(true); - console.log('Game result dialog should be shown now'); + timer(1500).subscribe(() => { + this.showGameResult.set(true); + console.log('Game result dialog shown after delay'); + }); } } @@ -180,21 +181,18 @@ export default class BlackjackComponent { onCloseGameResult(): void { console.log('Closing game result dialog'); this.showGameResult.set(false); + this.userService.refreshCurrentUser(); } private handleGameError(error: HttpErrorResponse): void { if (error instanceof HttpErrorResponse) { if (error.status === 400 && error.error?.error === 'Invalid state') { this.gameInProgress.set(false); - - this.refreshUserBalance(); + this.userService.refreshCurrentUser(); } else if (error.status === 500) { console.log('Server error occurred. The game may have been updated in another session.'); - this.gameInProgress.set(false); - - this.refreshUserBalance(); - + this.userService.refreshCurrentUser(); if (this.currentGameId()) { this.refreshGameState(this.currentGameId()!); } 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 fea5453..9fa4c83 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 @@ -6,9 +6,11 @@ import { OnChanges, Output, SimpleChanges, + signal, } from '@angular/core'; import { CommonModule, CurrencyPipe } from '@angular/common'; -import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { BettingService } from '@blackjack/services/betting.service'; @Component({ selector: 'app-game-info', @@ -101,7 +103,14 @@ import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angula changeDetection: ChangeDetectionStrategy.OnPush, }) export class GameInfoComponent implements OnChanges { - @Input() balance = 0; + @Input() set balance(value: number) { + this._balance.set(value); + } + get balance() { + return this._balance(); + } + private _balance = signal(0); + @Input() currentBet = 0; @Input() gameInProgress = false; @Input() isActionInProgress = false; @@ -109,24 +118,19 @@ export class GameInfoComponent implements OnChanges { betForm: FormGroup; - constructor(private fb: FormBuilder) { - this.betForm = this.fb.group({ - bet: ['', [Validators.required, Validators.min(1)]], - }); + constructor(private bettingService: BettingService) { + this.betForm = this.bettingService.createBetForm(); } 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(); + this.bettingService.updateBetFormValidators(this.betForm, this.balance); } } setBetAmount(percentage: number) { - const betAmount = Math.floor(this.balance * percentage * 100) / 100; - if (betAmount >= 1) { + const betAmount = this.bettingService.calculateBetAmount(this.balance, percentage); + if (this.bettingService.isValidBet(betAmount, this.balance)) { this.betForm.patchValue({ bet: betAmount }); } } @@ -134,7 +138,7 @@ export class GameInfoComponent implements OnChanges { onSubmit() { if (this.betForm.valid) { const betAmount = parseFloat(this.betForm.value.bet); - if (betAmount <= this.balance) { + if (this.bettingService.isValidBet(betAmount, this.balance)) { this.newGame.emit(betAmount); this.betForm.reset(); } diff --git a/frontend/src/app/feature/game/blackjack/services/betting.service.ts b/frontend/src/app/feature/game/blackjack/services/betting.service.ts new file mode 100644 index 0000000..347f19b --- /dev/null +++ b/frontend/src/app/feature/game/blackjack/services/betting.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +@Injectable({ + providedIn: 'root', +}) +export class BettingService { + constructor(private fb: FormBuilder) {} + + createBetForm(): FormGroup { + return this.fb.group({ + bet: ['', [Validators.required, Validators.min(1)]], + }); + } + + updateBetFormValidators(form: FormGroup, balance: number): void { + form.reset(); + form + .get('bet') + ?.setValidators([Validators.required, Validators.min(1), Validators.max(balance)]); + form.get('bet')?.updateValueAndValidity(); + } + + calculateBetAmount(balance: number, percentage: number): number { + return Math.floor(balance * percentage * 100) / 100; + } + + isValidBet(betAmount: number, balance: number): boolean { + return betAmount >= 1 && betAmount <= balance; + } +} From e9c4d4fbed143afd4d980d3b83090355804b2e8f Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 2 Apr 2025 11:29:16 +0200 Subject: [PATCH 2/3] refactor(BlackJackService): simplify card dealing logic --- .../java/de/szut/casino/blackjack/BlackJackService.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 a37a788..ebddd1a 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java @@ -46,11 +46,7 @@ public class BlackJackService { return game; } - if (game.isSplit()) { - dealCardToPlayer(game); - } else { - dealCardToPlayer(game); - } + dealCardToPlayer(game); updateGameStateAndBalance(game); From d22c4c243f8a14a1f37373e8a12039b4a72645ef Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 2 Apr 2025 11:38:14 +0200 Subject: [PATCH 3/3] feat(game-info): add placeholder and disable bet input --- .../blackjack/components/game-info/game-info.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 9fa4c83..df29e81 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 @@ -67,10 +67,12 @@ import { BettingService } from '@blackjack/services/betting.service'; type="number" id="bet" formControlName="bet" - class="w-full px-3 py-2 bg-deep-blue-light text-white rounded focus:outline-none focus:ring-2 focus:ring-emerald" + class="w-full px-3 py-2 bg-deep-blue-light text-white rounded focus:outline-none focus:ring-2 focus:ring-emerald disabled:opacity-50" [min]="1" [max]="balance" step="0.01" + [disabled]="gameInProgress || isActionInProgress" + [placeholder]="balance | currency: 'EUR'" /> @if (betForm.get('bet')?.errors?.['required'] && betForm.get('bet')?.touched) { Bitte geben Sie einen Einsatz ein