Merge branch 'main' into feat/lootboxes
All checks were successful
CI / Get Changed Files (pull_request) Successful in 7s
CI / eslint (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 38s

This commit is contained in:
Phan Huy Tran 2025-04-02 09:15:31 +00:00
commit ec994616ee
Signed by:
GPG key ID: 944223E4D46B7412
25 changed files with 884 additions and 111 deletions

View file

@ -26,6 +26,23 @@ public class BlackJackGameController {
this.userService = userService;
}
@GetMapping("/blackjack/{id}")
public ResponseEntity<Object> getGame(@PathVariable Long id, @RequestHeader("Authorization") String token) {
Optional<UserEntity> 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(game);
}
@PostMapping("/blackjack/{id}/hit")
public ResponseEntity<Object> hit(@PathVariable Long id, @RequestHeader("Authorization") String token) {
Optional<UserEntity> optionalUser = userService.getCurrentUser(token);
@ -40,13 +57,41 @@ public class BlackJackGameController {
return ResponseEntity.notFound().build();
}
if (game.getState() != BlackJackState.IN_PROGRESS) {
Map<String, String> errorResponse = new HashMap<>();
errorResponse.put("error", "Invalid state");
return ResponseEntity.badRequest().body(errorResponse);
return ResponseEntity.ok(blackJackService.hit(game));
}
@PostMapping("/blackjack/{id}/stand")
public ResponseEntity<Object> stand(@PathVariable Long id, @RequestHeader("Authorization") String token) {
Optional<UserEntity> optionalUser = userService.getCurrentUser(token);
if (optionalUser.isEmpty()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(blackJackService.hit(game, user));
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.stand(game));
}
@PostMapping("/blackjack/{id}/doubleDown")
public ResponseEntity<Object> doubleDown(@PathVariable Long id, @RequestHeader("Authorization") String token) {
Optional<UserEntity> 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")

View file

@ -29,7 +29,6 @@ public class BlackJackGameEntity {
@JsonIgnore
private UserEntity user;
// Expose UserID to JSON output
public Long getUserId() {
return user != null ? user.getId() : null;
}

View file

@ -3,6 +3,7 @@ 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;
@ -13,52 +14,167 @@ 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<BlackJackGameEntity> optionalBlackJackGame = blackJackGameRepository.findById(id);
return optionalBlackJackGame.orElse(null);
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);
}
for (int i = 0; i < 2; i++) {
CardEntity playerCard = drawCardFromDeck(game);
playerCard.setCardType(CardType.PLAYER);
game.getPlayerCards().add(playerCard);
@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);
}
CardEntity dealerCard = drawCardFromDeck(game);
dealerCard.setCardType(CardType.DEALER);
game.getDealerCards().add(dealerCard);
BlackJackState state = handleState(game, user);
game.setState(state);
userRepository.save(user);
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;
}
public BlackJackGameEntity hit(BlackJackGameEntity game, UserEntity user) {
CardEntity drawnCard = drawCardFromDeck(game);
drawnCard.setCardType(CardType.PLAYER);
game.getPlayerCards().add(drawnCard);
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());
game.setState(handleState(game, user));
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);
}
return blackJackGameRepository.save(game);
@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) {
@ -84,7 +200,7 @@ public class BlackJackService {
return game.getDeck().removeFirst();
}
private BlackJackState handleState(BlackJackGameEntity game, UserEntity user) {
private BlackJackState getState(BlackJackGameEntity game) {
int playerHandValue = calculateHandValue(game.getPlayerCards());
if (playerHandValue == 21) {
@ -95,14 +211,14 @@ public class BlackJackService {
int dealerHandValue = calculateHandValue(game.getDealerCards());
if (dealerHandValue == 21) {
return BlackJackState.STANDOFF;
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) {
user.setBalance(user.getBalance().subtract(game.getBet()));
return BlackJackState.PLAYER_LOST;
}

View file

@ -4,5 +4,6 @@ public enum BlackJackState {
IN_PROGRESS,
PLAYER_BLACKJACK,
PLAYER_LOST,
STANDOFF,
PLAYER_WON,
DRAW,
}