From 9175c82f98210736ab555413c5924c089d7809ef Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 21 May 2025 08:58:25 +0200 Subject: [PATCH 1/6] feat: implement dice game api --- .../de/szut/casino/dice/DiceController.java | 44 ++++++++++++ .../java/de/szut/casino/dice/DiceDto.java | 25 +++++++ .../java/de/szut/casino/dice/DiceResult.java | 39 +++++++++++ .../java/de/szut/casino/dice/DiceService.java | 68 +++++++++++++++++++ 4 files changed, 176 insertions(+) create mode 100644 backend/src/main/java/de/szut/casino/dice/DiceController.java create mode 100644 backend/src/main/java/de/szut/casino/dice/DiceDto.java create mode 100644 backend/src/main/java/de/szut/casino/dice/DiceResult.java create mode 100644 backend/src/main/java/de/szut/casino/dice/DiceService.java diff --git a/backend/src/main/java/de/szut/casino/dice/DiceController.java b/backend/src/main/java/de/szut/casino/dice/DiceController.java new file mode 100644 index 0000000..949a002 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/dice/DiceController.java @@ -0,0 +1,44 @@ +package de.szut.casino.dice; + +import de.szut.casino.exceptionHandling.exceptions.InsufficientFundsException; +import de.szut.casino.exceptionHandling.exceptions.UserNotFoundException; +import de.szut.casino.shared.service.BalanceService; +import de.szut.casino.user.UserEntity; +import de.szut.casino.user.UserService; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Optional; + +@RestController +public class DiceController { + private final UserService userService; + private final BalanceService balanceService; + private final DiceService diceService; + + public DiceController(UserService userService, BalanceService balanceService, DiceService diceService) { + this.userService = userService; + this.balanceService = balanceService; + this.diceService = diceService; + } + + @PostMapping("/dice") + public ResponseEntity rollDice(@RequestBody @Valid DiceDto diceDto) { + Optional optionalUser = userService.getCurrentUser(); + + if (optionalUser.isEmpty()) { + throw new UserNotFoundException(); + } + + UserEntity user = optionalUser.get(); + + if (!this.balanceService.hasFunds(user, diceDto)) { + throw new InsufficientFundsException(); + } + + return ResponseEntity.ok(diceService.play(user, diceDto)); + } +} diff --git a/backend/src/main/java/de/szut/casino/dice/DiceDto.java b/backend/src/main/java/de/szut/casino/dice/DiceDto.java new file mode 100644 index 0000000..ccec485 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/dice/DiceDto.java @@ -0,0 +1,25 @@ +package de.szut.casino.dice; + +import de.szut.casino.shared.dto.BetDto; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.math.BigDecimal; + +@Getter +@Setter +public class DiceDto extends BetDto { + private boolean rollOver; + + @NotNull + @DecimalMin(value = "1.00") + private BigDecimal targetValue; + + public DiceDto(BigDecimal betAmount, boolean rollOver, BigDecimal targetValue) { + super(betAmount); + this.rollOver = rollOver; + this.targetValue = targetValue; + } +} diff --git a/backend/src/main/java/de/szut/casino/dice/DiceResult.java b/backend/src/main/java/de/szut/casino/dice/DiceResult.java new file mode 100644 index 0000000..5a1c797 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/dice/DiceResult.java @@ -0,0 +1,39 @@ +package de.szut.casino.dice; + +import java.math.BigDecimal; + +public class DiceResult { + private boolean win; + private BigDecimal payout; + private BigDecimal rolledValue; + + public DiceResult(boolean win, BigDecimal payout, BigDecimal rolledValue) { + this.win = win; + this.payout = payout; + this.rolledValue = rolledValue; + } + + public boolean isWin() { + return win; + } + + public void setWin(boolean win) { + this.win = win; + } + + public BigDecimal getPayout() { + return payout; + } + + public void setPayout(BigDecimal payout) { + this.payout = payout; + } + + public BigDecimal getRolledValue() { + return rolledValue; + } + + public void setRolledValue(BigDecimal rolledValue) { + this.rolledValue = rolledValue; + } +} diff --git a/backend/src/main/java/de/szut/casino/dice/DiceService.java b/backend/src/main/java/de/szut/casino/dice/DiceService.java new file mode 100644 index 0000000..71e4584 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/dice/DiceService.java @@ -0,0 +1,68 @@ +package de.szut.casino.dice; + +import de.szut.casino.shared.service.BalanceService; +import de.szut.casino.user.UserEntity; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Random; + +@Service +public class DiceService { + private static final int MAX_DICE_VALUE = 100; + private final Random random = new Random(); + private final BalanceService balanceService; + + public DiceService(BalanceService balanceService) { + this.balanceService = balanceService; + } + + public DiceResult play(UserEntity user, DiceDto diceDto) { + balanceService.subtractFunds(user, diceDto.getBetAmount()); + + int rolledValue = random.nextInt(MAX_DICE_VALUE) + 1; + BigDecimal rolledValueDecimal = BigDecimal.valueOf(rolledValue); + + BigDecimal targetValue = diceDto.getTargetValue(); + boolean isRollOver = diceDto.isRollOver(); + + boolean winConditionMet = isWinConditionMet(rolledValueDecimal, targetValue, isRollOver); + + if (!winConditionMet) { + return new DiceResult(false, BigDecimal.ZERO, rolledValueDecimal); + } + + BigDecimal winChance = calculateWinChance(targetValue, isRollOver); + BigDecimal multiplier = calculateMultiplier(winChance); + + BigDecimal payout = diceDto.getBetAmount().multiply(multiplier); + balanceService.addFunds(user, payout); + + return new DiceResult(true, payout, rolledValueDecimal); + } + + private boolean isWinConditionMet(BigDecimal rolledValue, BigDecimal targetValue, boolean isRollOver) { + if (isRollOver) { + return rolledValue.compareTo(targetValue) > 0; + } + + return rolledValue.compareTo(targetValue) < 0; + } + + private BigDecimal calculateWinChance(BigDecimal targetValue, boolean isRollOver) { + if (isRollOver) { + return BigDecimal.valueOf(MAX_DICE_VALUE).subtract(targetValue); + } + + return targetValue.subtract(BigDecimal.ONE); + } + + private BigDecimal calculateMultiplier(BigDecimal winChance) { + if (winChance.compareTo(BigDecimal.ZERO) > 0) { + return BigDecimal.valueOf(MAX_DICE_VALUE - 1).divide(winChance, 4, RoundingMode.HALF_UP); + } + + return BigDecimal.ZERO; + } +} From d67019007335083ca3fa8a35a8a03f96fd429637 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 21 May 2025 08:59:08 +0200 Subject: [PATCH 2/6] refactor: use lombok --- .../java/de/szut/casino/dice/DiceResult.java | 29 ++++--------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/backend/src/main/java/de/szut/casino/dice/DiceResult.java b/backend/src/main/java/de/szut/casino/dice/DiceResult.java index 5a1c797..65a7f69 100644 --- a/backend/src/main/java/de/szut/casino/dice/DiceResult.java +++ b/backend/src/main/java/de/szut/casino/dice/DiceResult.java @@ -1,7 +1,12 @@ package de.szut.casino.dice; +import lombok.Getter; +import lombok.Setter; + import java.math.BigDecimal; +@Setter +@Getter public class DiceResult { private boolean win; private BigDecimal payout; @@ -12,28 +17,4 @@ public class DiceResult { this.payout = payout; this.rolledValue = rolledValue; } - - public boolean isWin() { - return win; - } - - public void setWin(boolean win) { - this.win = win; - } - - public BigDecimal getPayout() { - return payout; - } - - public void setPayout(BigDecimal payout) { - this.payout = payout; - } - - public BigDecimal getRolledValue() { - return rolledValue; - } - - public void setRolledValue(BigDecimal rolledValue) { - this.rolledValue = rolledValue; - } } From d02c3d24f192c64d85a545af3757b117798e3bf0 Mon Sep 17 00:00:00 2001 From: Jan K9f Date: Wed, 21 May 2025 09:02:13 +0200 Subject: [PATCH 3/6] fix: Fix bug where the password reset doesnt redirect to login --- .../auth/recover-password/recover-password.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/app/feature/auth/recover-password/recover-password.component.ts b/frontend/src/app/feature/auth/recover-password/recover-password.component.ts index 3695d57..905f200 100644 --- a/frontend/src/app/feature/auth/recover-password/recover-password.component.ts +++ b/frontend/src/app/feature/auth/recover-password/recover-password.component.ts @@ -87,6 +87,10 @@ export class RecoverPasswordComponent implements OnInit { 'Wenn ein Konto mit dieser E-Mail existiert, wird eine E-Mail mit weiteren Anweisungen gesendet.' ); this.emailForm.reset(); + setTimeout(() => { + this.closeDialog.emit(); + this.switchToLogin.emit(); + }, 2000); }, error: (err) => { this.isLoading.set(false); From 34c7c39b637f96355314e88cbc3420990ed25995 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 21 May 2025 09:15:03 +0200 Subject: [PATCH 4/6] fix: Add missing dice target value validation --- backend/src/main/java/de/szut/casino/dice/DiceDto.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/main/java/de/szut/casino/dice/DiceDto.java b/backend/src/main/java/de/szut/casino/dice/DiceDto.java index ccec485..ecbf3d7 100644 --- a/backend/src/main/java/de/szut/casino/dice/DiceDto.java +++ b/backend/src/main/java/de/szut/casino/dice/DiceDto.java @@ -1,6 +1,7 @@ package de.szut.casino.dice; import de.szut.casino.shared.dto.BetDto; +import jakarta.validation.constraints.DecimalMax; import jakarta.validation.constraints.DecimalMin; import jakarta.validation.constraints.NotNull; import lombok.Getter; @@ -15,6 +16,7 @@ public class DiceDto extends BetDto { @NotNull @DecimalMin(value = "1.00") + @DecimalMax(value = "100") private BigDecimal targetValue; public DiceDto(BigDecimal betAmount, boolean rollOver, BigDecimal targetValue) { From 1dfdedee91444c52a39d9c851a4a8e6272366c70 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 21 May 2025 09:49:09 +0200 Subject: [PATCH 5/6] fix: delete orhpaned blackjack games --- .../casino/blackjack/BlackJackService.java | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 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 cb31352..d0a3839 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java @@ -35,8 +35,8 @@ public class BlackJackService { game.setState(getState(game)); deductBetFromBalance(user, betAmount); - - return blackJackGameRepository.save(game); + + return processGameBasedOnState(game); } @Transactional @@ -44,12 +44,11 @@ public class BlackJackService { if (game.getState() != BlackJackState.IN_PROGRESS) { return game; } - + dealCardToPlayer(game); - updateGameStateAndBalance(game); - - return blackJackGameRepository.save(game); + + return processGameBasedOnState(game); } @Transactional @@ -57,11 +56,11 @@ public class BlackJackService { if (game.getState() != BlackJackState.IN_PROGRESS) { return game; } - + dealCardsToDealerUntilMinimumScore(game); determineWinnerAndUpdateBalance(game); - - return blackJackGameRepository.save(game); + + return processGameBasedOnState(game); } @Transactional @@ -69,50 +68,59 @@ public class BlackJackService { if (game.getState() != BlackJackState.IN_PROGRESS || game.getPlayerCards().size() != 2) { return game; } - + UserEntity user = getUserWithFreshData(game.getUser()); BigDecimal additionalBet = game.getBet(); - + 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 || + 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 processGameBasedOnState(BlackJackGameEntity game) { + if (game.getState() != BlackJackState.IN_PROGRESS) { + this.blackJackGameRepository.delete(game); + return game; + } + return blackJackGameRepository.save(game); } From 1931a023691a8ca2ae54ae9a827f0a7b7d1da9bb Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 21 May 2025 09:50:00 +0200 Subject: [PATCH 6/6] style: format code --- .../casino/blackjack/BlackJackService.java | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 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 d0a3839..9bf591f 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java @@ -29,10 +29,10 @@ public class BlackJackService { BlackJackGameEntity game = new BlackJackGameEntity(); game.setUser(user); game.setBet(betAmount); - + initializeDeck(game); dealInitialCards(game); - + game.setState(getState(game)); deductBetFromBalance(user, betAmount); @@ -88,9 +88,9 @@ public class BlackJackService { @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())) { + game.getPlayerCards().size() != 2 || + game.isSplit() || + !game.getPlayerCards().get(0).getRank().equals(game.getPlayerCards().get(1).getRank())) { return game; } @@ -127,48 +127,48 @@ public class BlackJackService { 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); @@ -179,7 +179,7 @@ public class BlackJackService { } } else { game.setState(getState(game)); - + if (game.getState() == BlackJackState.PLAYER_WON) { updateUserBalance(game, true); } else if (game.getState() == BlackJackState.PLAYER_LOST) { @@ -187,7 +187,7 @@ public class BlackJackService { } } } - + private void determineWinnerAndUpdateBalance(BlackJackGameEntity game) { int playerValue = calculateHandValue(game.getPlayerCards()); int dealerValue = calculateHandValue(game.getDealerCards()); @@ -203,7 +203,7 @@ public class BlackJackService { updateUserBalance(game, false); } } - + private void deductBetFromBalance(UserEntity user, BigDecimal betAmount) { user.setBalance(user.getBalance().subtract(betAmount)); userRepository.save(user); @@ -214,21 +214,21 @@ public class BlackJackService { 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) { @@ -244,7 +244,7 @@ public class BlackJackService { balance = balance.add(totalBet); } } - + user.setBalance(balance); userRepository.save(user); }