Compare commits

...

11 commits

Author SHA1 Message Date
e4cd62cca4
Merge pull request 'fix: delete orhpaned blackjack games' (!207) from fix-delete-completed-blackjack-games into main
All checks were successful
Release / Release (push) Successful in 58s
Release / Build Frontend Image (push) Successful in 29s
Release / Build Backend Image (push) Successful in 31s
Reviewed-on: #207
Reviewed-by: Jan K9f <jan@kjan.email>
2025-05-21 07:54:01 +00:00
Phan Huy Tran
1931a02369 style: format code
All checks were successful
CI / Get Changed Files (pull_request) Successful in 9s
CI / eslint (pull_request) Has been skipped
CI / Docker frontend validation (pull_request) Has been skipped
CI / oxlint (pull_request) Has been skipped
CI / prettier (pull_request) Has been skipped
CI / test-build (pull_request) Has been skipped
CI / Checkstyle Main (pull_request) Successful in 1m6s
CI / Docker backend validation (pull_request) Successful in 1m3s
2025-05-21 09:50:00 +02:00
Phan Huy Tran
1dfdedee91 fix: delete orhpaned blackjack games
All checks were successful
CI / Get Changed Files (pull_request) Successful in 7s
CI / eslint (pull_request) Has been skipped
CI / Docker frontend validation (pull_request) Has been skipped
CI / oxlint (pull_request) Has been skipped
CI / prettier (pull_request) Has been skipped
CI / test-build (pull_request) Has been skipped
CI / Checkstyle Main (pull_request) Successful in 57s
CI / Docker backend validation (pull_request) Successful in 1m14s
2025-05-21 09:49:09 +02:00
61b806c048
Merge pull request 'fix: Add missing dice target value validation' (!205) from fix-target-value-validation into main
Some checks failed
Release / Release (push) Failing after 10m37s
Release / Build Backend Image (push) Has been cancelled
Release / Build Frontend Image (push) Has been cancelled
Reviewed-on: #205
Reviewed-by: Jan K9f <jan@kjan.email>
2025-05-21 07:16:44 +00:00
Phan Huy Tran
34c7c39b63 fix: Add missing dice target value validation
All checks were successful
CI / Get Changed Files (pull_request) Successful in 7s
CI / eslint (pull_request) Has been skipped
CI / oxlint (pull_request) Has been skipped
CI / Docker frontend validation (pull_request) Has been skipped
CI / prettier (pull_request) Has been skipped
CI / test-build (pull_request) Has been skipped
CI / Checkstyle Main (pull_request) Successful in 50s
CI / Docker backend validation (pull_request) Successful in 53s
2025-05-21 09:15:07 +02:00
1966313a20
Merge pull request 'feat: implement dice game api (CAS-74)' (!203) from feat-dice into main
Some checks failed
Release / Build Backend Image (push) Blocked by required conditions
Release / Build Frontend Image (push) Blocked by required conditions
Release / Release (push) Has been cancelled
Reviewed-on: #203
Reviewed-by: Jan K9f <jan@kjan.email>
2025-05-21 07:10:07 +00:00
816c659b5c
Merge pull request 'fix: Fix bug where the password reset doesnt redirect to login (CAS-82)' (!204) from bugfix/password-reset-redirect into main
Some checks failed
Release / Build Backend Image (push) Blocked by required conditions
Release / Build Frontend Image (push) Blocked by required conditions
Release / Release (push) Has been cancelled
Reviewed-on: #204
Reviewed-by: Constantin Simonis <constantin@simonis.lol>
2025-05-21 07:10:02 +00:00
d02c3d24f1 fix: Fix bug where the password reset doesnt redirect to login
All checks were successful
CI / Get Changed Files (pull_request) Successful in 7s
CI / Checkstyle Main (pull_request) Has been skipped
CI / Docker backend validation (pull_request) Has been skipped
CI / Docker frontend validation (pull_request) Successful in 18s
CI / oxlint (pull_request) Successful in 18s
CI / eslint (pull_request) Successful in 24s
CI / prettier (pull_request) Successful in 22s
CI / test-build (pull_request) Successful in 31s
2025-05-21 09:02:31 +02:00
0d59b63c23
Merge branch 'main' into feat-dice
All checks were successful
CI / Get Changed Files (pull_request) Successful in 13s
CI / eslint (pull_request) Has been skipped
CI / Docker frontend validation (pull_request) Has been skipped
CI / oxlint (pull_request) Has been skipped
CI / prettier (pull_request) Has been skipped
CI / test-build (pull_request) Has been skipped
CI / Docker backend validation (pull_request) Successful in 13s
CI / Checkstyle Main (pull_request) Successful in 2m39s
2025-05-21 07:02:16 +00:00
Phan Huy Tran
d670190073 refactor: use lombok
All checks were successful
CI / Get Changed Files (pull_request) Successful in 30s
CI / eslint (pull_request) Has been skipped
CI / Docker frontend validation (pull_request) Has been skipped
CI / oxlint (pull_request) Has been skipped
CI / prettier (pull_request) Has been skipped
CI / test-build (pull_request) Has been skipped
CI / Docker backend validation (pull_request) Successful in 1m1s
CI / Checkstyle Main (pull_request) Successful in 4m2s
2025-05-21 08:59:08 +02:00
Phan Huy Tran
9175c82f98 feat: implement dice game api 2025-05-21 08:58:25 +02:00
6 changed files with 214 additions and 43 deletions

View file

@ -29,14 +29,14 @@ public class BlackJackService {
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);
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,98 +68,107 @@ 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 ||
game.isSplit() ||
!game.getPlayerCards().get(0).getRank().equals(game.getPlayerCards().get(1).getRank())) {
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);
}
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);
@ -171,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) {
@ -179,7 +187,7 @@ public class BlackJackService {
}
}
}
private void determineWinnerAndUpdateBalance(BlackJackGameEntity game) {
int playerValue = calculateHandValue(game.getPlayerCards());
int dealerValue = calculateHandValue(game.getDealerCards());
@ -195,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);
@ -206,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) {
@ -236,7 +244,7 @@ public class BlackJackService {
balance = balance.add(totalBet);
}
}
user.setBalance(balance);
userRepository.save(user);
}

View file

@ -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<Object> rollDice(@RequestBody @Valid DiceDto diceDto) {
Optional<UserEntity> 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));
}
}

View file

@ -0,0 +1,27 @@
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;
import lombok.Setter;
import java.math.BigDecimal;
@Getter
@Setter
public class DiceDto extends BetDto {
private boolean rollOver;
@NotNull
@DecimalMin(value = "1.00")
@DecimalMax(value = "100")
private BigDecimal targetValue;
public DiceDto(BigDecimal betAmount, boolean rollOver, BigDecimal targetValue) {
super(betAmount);
this.rollOver = rollOver;
this.targetValue = targetValue;
}
}

View file

@ -0,0 +1,20 @@
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;
private BigDecimal rolledValue;
public DiceResult(boolean win, BigDecimal payout, BigDecimal rolledValue) {
this.win = win;
this.payout = payout;
this.rolledValue = rolledValue;
}
}

View file

@ -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;
}
}

View file

@ -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);