From c9f71f70fa8746db627ac15f308c114c17f26558 Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Thu, 27 Mar 2025 15:40:26 +0100 Subject: [PATCH] feat: add double down feature to blackjack game --- .../blackjack/BlackJackGameController.java | 17 ++ .../casino/blackjack/BlackJackService.java | 150 ++++++++++++------ .../game/blackjack/blackjack.component.html | 2 + .../game/blackjack/blackjack.component.ts | 35 +++- .../game-controls/game-controls.component.ts | 8 + .../game-result/game-result.component.ts | 22 ++- .../blackjack/services/blackjack.service.ts | 11 ++ 7 files changed, 188 insertions(+), 57 deletions(-) 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 9f1dae9..a10fe63 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameController.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameController.java @@ -77,6 +77,23 @@ public class BlackJackGameController { return ResponseEntity.ok(blackJackService.stand(game)); } + @PostMapping("/blackjack/{id}/doubleDown") + public ResponseEntity doubleDown(@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.doubleDown(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/BlackJackService.java b/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java index 801acaa..5c39459 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java @@ -14,17 +14,15 @@ import java.util.Random; 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; } - private final Random random = new Random(); - public BlackJackGameEntity getBlackJackGame(Long id) { - Optional optionalBlackJackGame = blackJackGameRepository.findById(id); - return optionalBlackJackGame.orElse(null); + return blackJackGameRepository.findById(id).orElse(null); } @Transactional @@ -32,88 +30,147 @@ public class BlackJackService { BlackJackGameEntity game = new BlackJackGameEntity(); game.setUser(user); game.setBet(betAmount); + initializeDeck(game); - - for (int i = 0; i < 2; i++) { - CardEntity playerCard = drawCardFromDeck(game); - playerCard.setCardType(CardType.PLAYER); - game.getPlayerCards().add(playerCard); - } - - CardEntity dealerCard = drawCardFromDeck(game); - dealerCard.setCardType(CardType.DEALER); - game.getDealerCards().add(dealerCard); - + dealInitialCards(game); + game.setState(getState(game)); - user.setBalance(user.getBalance().subtract(betAmount)); - - userRepository.save(user); + deductBetFromBalance(user, betAmount); + return blackJackGameRepository.save(game); } @Transactional public BlackJackGameEntity hit(BlackJackGameEntity game) { - game = blackJackGameRepository.findById(game.getId()).orElse(game); + game = refreshGameState(game); if (game.getState() != BlackJackState.IN_PROGRESS) { return game; } - CardEntity drawnCard = drawCardFromDeck(game); - drawnCard.setCardType(CardType.PLAYER); - game.getPlayerCards().add(drawnCard); - - game.setState(getState(game)); - - if (game.getState() == BlackJackState.PLAYER_WON) { - updateUserBalance(game, true); - } else if (game.getState() == BlackJackState.PLAYER_LOST) { - updateUserBalance(game, false); - } - + dealCardToPlayer(game); + updateGameStateAndBalance(game); + return blackJackGameRepository.save(game); } @Transactional public BlackJackGameEntity stand(BlackJackGameEntity game) { - game = blackJackGameRepository.findById(game.getId()).orElse(game); + game = refreshGameState(game); if (game.getState() != BlackJackState.IN_PROGRESS) { return game; } - while (calculateHandValue(game.getDealerCards()) < 17) { - CardEntity dealerCard = drawCardFromDeck(game); - dealerCard.setCardType(CardType.DEALER); - game.getDealerCards().add(dealerCard); - } + 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); + updateUserBalance(game, false); // For draw, player gets their bet back } - - return blackJackGameRepository.save(game); + } + + 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 = game.getUser(); - user = userRepository.findById(user.getId()).orElse(user); - BigDecimal balance = user.getBalance(); - + 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) { + } else if (game.getState() == BlackJackState.DRAW) { balance = balance.add(betAmount); } @@ -159,6 +216,7 @@ public class BlackJackService { private int calculateHandValue(List hand) { int sum = 0; int aceCount = 0; + for (CardEntity card : hand) { sum += card.getRank().getValue(); if (card.getRank() == Rank.ACE) { diff --git a/frontend/src/app/feature/game/blackjack/blackjack.component.html b/frontend/src/app/feature/game/blackjack/blackjack.component.html index 27860a1..debbb75 100644 --- a/frontend/src/app/feature/game/blackjack/blackjack.component.html +++ b/frontend/src/app/feature/game/blackjack/blackjack.component.html @@ -11,6 +11,7 @@ [gameState]="gameState()" (hit)="onHit()" (stand)="onStand()" + (doubleDown)="onDoubleDown()" (leave)="leaveGame()" > } @@ -32,4 +33,5 @@ [gameState]="gameState()" [amount]="currentBet()" [show]="showGameResult()" + (close)="onCloseGameResult()" > diff --git a/frontend/src/app/feature/game/blackjack/blackjack.component.ts b/frontend/src/app/feature/game/blackjack/blackjack.component.ts index 2e29eb0..57a04fc 100644 --- a/frontend/src/app/feature/game/blackjack/blackjack.component.ts +++ b/frontend/src/app/feature/game/blackjack/blackjack.component.ts @@ -60,10 +60,13 @@ export default class BlackjackComponent { this.gameInProgress.set(game.state === 'IN_PROGRESS'); this.gameState.set(game.state); + // When game ends, make sure all dealer cards are visible + const isGameOver = game.state !== 'IN_PROGRESS'; + this.dealerCards.set( game.dealerCards.map((card, index) => ({ ...card, - hidden: index === 1 && game.state === 'IN_PROGRESS', + hidden: !isGameOver && index === 1 && game.state === 'IN_PROGRESS', })) ); @@ -74,11 +77,14 @@ export default class BlackjackComponent { })) ); - if (game.state !== 'IN_PROGRESS') { + // Only refresh and show game result if the game has ended + if (isGameOver) { + console.log('Game is over, state:', game.state); this.refreshUserBalance(); - setTimeout(() => { - this.showGameResult.set(true); - }, 1000); + + // Show result immediately without resetting first + this.showGameResult.set(true); + console.log('Game result dialog should be shown now'); } } @@ -127,7 +133,26 @@ export default class BlackjackComponent { }); } + onDoubleDown(): void { + if (!this.currentGameId()) return; + + if (this.gameState() !== 'IN_PROGRESS' || this.playerCards().length !== 2) { + return; + } + + this.blackjackService.doubleDown(this.currentGameId()!).subscribe({ + next: (game) => { + this.updateGameState(game); + }, + error: (error) => { + console.error('Failed to double down:', error); + this.handleGameError(error); + }, + }); + } + onCloseGameResult(): void { + console.log('Closing game result dialog'); this.showGameResult.set(false); } diff --git a/frontend/src/app/feature/game/blackjack/components/game-controls/game-controls.component.ts b/frontend/src/app/feature/game/blackjack/components/game-controls/game-controls.component.ts index 73c5928..3e75315 100644 --- a/frontend/src/app/feature/game/blackjack/components/game-controls/game-controls.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/game-controls/game-controls.component.ts @@ -31,6 +31,13 @@ import { Card } from '../../models/blackjack.model'; > Halten +