diff --git a/backend/requests/blackjack.http b/backend/requests/blackjack.http index 21dd35e..0b8b261 100644 --- a/backend/requests/blackjack.http +++ b/backend/requests/blackjack.http @@ -10,11 +10,3 @@ Content-Type: application/json POST http://localhost:8080/blackjack/54/hit Authorization: Bearer {{token}} -### -POST http://localhost:8080/blackjack/202/stand -Authorization: Bearer {{token}} - -### -GET http://localhost:8080/blackjack/202 -Authorization: Bearer {{token}} - 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..774e0c1 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameController.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameController.java @@ -26,23 +26,6 @@ public class BlackJackGameController { this.userService = userService; } - @GetMapping("/blackjack/{id}") - public ResponseEntity getGame(@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(game); - } - @PostMapping("/blackjack/{id}/hit") public ResponseEntity hit(@PathVariable Long id, @RequestHeader("Authorization") String token) { Optional optionalUser = userService.getCurrentUser(token); @@ -57,41 +40,13 @@ public class BlackJackGameController { return ResponseEntity.notFound().build(); } - return ResponseEntity.ok(blackJackService.hit(game)); - } - - @PostMapping("/blackjack/{id}/stand") - public ResponseEntity stand(@PathVariable Long id, @RequestHeader("Authorization") String token) { - Optional optionalUser = userService.getCurrentUser(token); - - if (optionalUser.isEmpty()) { - return ResponseEntity.notFound().build(); + if (game.getState() != BlackJackState.IN_PROGRESS) { + Map errorResponse = new HashMap<>(); + errorResponse.put("error", "Invalid state"); + return ResponseEntity.badRequest().body(errorResponse); } - 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 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)); + return ResponseEntity.ok(blackJackService.hit(game, user)); } @PostMapping("/blackjack/start") 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..eb5a704 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameEntity.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameEntity.java @@ -29,6 +29,7 @@ public class BlackJackGameEntity { @JsonIgnore private UserEntity user; + // Expose UserID to JSON output public Long getUserId() { return user != null ? user.getId() : null; } 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..693c055 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java @@ -3,7 +3,6 @@ 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; @@ -14,167 +13,52 @@ 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) { - return blackJackGameRepository.findById(id).orElse(null); + Optional optionalBlackJackGame = blackJackGameRepository.findById(id); + return optionalBlackJackGame.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; + for (int i = 0; i < 2; i++) { + CardEntity playerCard = drawCardFromDeck(game); + playerCard.setCardType(CardType.PLAYER); + game.getPlayerCards().add(playerCard); } - - 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); - } + 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 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()); + public BlackJackGameEntity hit(BlackJackGameEntity game, UserEntity user) { + CardEntity drawnCard = drawCardFromDeck(game); + drawnCard.setCardType(CardType.PLAYER); + game.getPlayerCards().add(drawnCard); - 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); - } + game.setState(handleState(game, 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); + return blackJackGameRepository.save(game); } private void initializeDeck(BlackJackGameEntity game) { @@ -200,7 +84,7 @@ public class BlackJackService { return game.getDeck().removeFirst(); } - private BlackJackState getState(BlackJackGameEntity game) { + private BlackJackState handleState(BlackJackGameEntity game, UserEntity user) { int playerHandValue = calculateHandValue(game.getPlayerCards()); if (playerHandValue == 21) { @@ -211,14 +95,14 @@ public class BlackJackService { int dealerHandValue = calculateHandValue(game.getDealerCards()); if (dealerHandValue == 21) { - return BlackJackState.DRAW; + return BlackJackState.STANDOFF; } 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; } diff --git a/backend/src/main/java/de/szut/casino/blackjack/BlackJackState.java b/backend/src/main/java/de/szut/casino/blackjack/BlackJackState.java index 3f3e6fc..f896058 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackState.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackState.java @@ -4,6 +4,5 @@ public enum BlackJackState { IN_PROGRESS, PLAYER_BLACKJACK, PLAYER_LOST, - PLAYER_WON, - DRAW, + STANDOFF, } diff --git a/frontend/src/app/feature/deposit/deposit.component.ts b/frontend/src/app/feature/deposit/deposit.component.ts index 6cbae07..bb38fc9 100644 --- a/frontend/src/app/feature/deposit/deposit.component.ts +++ b/frontend/src/app/feature/deposit/deposit.component.ts @@ -16,12 +16,12 @@ import { } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { loadStripe, Stripe } from '@stripe/stripe-js'; +import { DepositService } from '../../service/deposit.service'; import { debounceTime } from 'rxjs'; +import { environment } from '../../../environments/environment'; import { NgIf } from '@angular/common'; +import { ModalAnimationService } from '../../shared/services/modal-animation.service'; import gsap from 'gsap'; -import { DepositService } from '@service/deposit.service'; -import { environment } from '@environments/environment'; -import { ModalAnimationService } from '@shared/services/modal-animation.service'; @Component({ selector: 'app-deposit', diff --git a/frontend/src/app/feature/game/blackjack/blackjack.component.html b/frontend/src/app/feature/game/blackjack/blackjack.component.html index 887aeeb..ae130d4 100644 --- a/frontend/src/app/feature/game/blackjack/blackjack.component.html +++ b/frontend/src/app/feature/game/blackjack/blackjack.component.html @@ -5,28 +5,10 @@
- - @if (isActionInProgress()) { -
-
-
- {{ currentAction() }} -
-
- } - @if (gameInProgress()) { } @@ -37,17 +19,8 @@ [balance]="balance()" [currentBet]="currentBet()" [gameInProgress]="gameInProgress()" - [isActionInProgress]="isActionInProgress()" (newGame)="onNewGame($event)" >
- - - diff --git a/frontend/src/app/feature/game/blackjack/blackjack.component.ts b/frontend/src/app/feature/game/blackjack/blackjack.component.ts index d6bfec4..174c358 100644 --- a/frontend/src/app/feature/game/blackjack/blackjack.component.ts +++ b/frontend/src/app/feature/game/blackjack/blackjack.component.ts @@ -1,18 +1,15 @@ import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { NavbarComponent } from '../../../shared/components/navbar/navbar.component'; import { Router } from '@angular/router'; +import { UserService } from '../../../service/user.service'; import { PlayingCardComponent } from './components/playing-card/playing-card.component'; import { DealerHandComponent } from './components/dealer-hand/dealer-hand.component'; import { PlayerHandComponent } from './components/player-hand/player-hand.component'; import { GameControlsComponent } from './components/game-controls/game-controls.component'; import { GameInfoComponent } from './components/game-info/game-info.component'; -import { Card, BlackjackGame } from '@blackjack/models/blackjack.model'; -import { BlackjackService } from '@blackjack/services/blackjack.service'; -import { HttpErrorResponse } from '@angular/common/http'; -import { GameResultComponent } from '@blackjack/components/game-result/game-result.component'; -import { GameState } from '@blackjack/enum/gameState'; -import { NavbarComponent } from '@shared/components/navbar/navbar.component'; -import { UserService } from '@service/user.service'; +import { Card, BlackjackGame } from './models/blackjack.model'; +import { BlackjackService } from './services/blackjack.service'; @Component({ selector: 'app-blackjack', @@ -25,7 +22,6 @@ import { UserService } from '@service/user.service'; PlayerHandComponent, GameControlsComponent, GameInfoComponent, - GameResultComponent, ], templateUrl: './blackjack.component.html', changeDetection: ChangeDetectionStrategy.OnPush, @@ -41,17 +37,8 @@ export default class BlackjackComponent { balance = signal(0); currentGameId = signal(undefined); gameInProgress = signal(false); - gameState = signal(GameState.IN_PROGRESS); - showGameResult = signal(false); - - isActionInProgress = signal(false); - currentAction = signal(''); constructor() { - this.refreshUserBalance(); - } - - private refreshUserBalance(): void { this.userService.getCurrentUser().subscribe((user) => { this.balance.set(user?.balance ?? 0); }); @@ -61,15 +48,12 @@ export default class BlackjackComponent { console.log('Game state update:', game); this.currentGameId.set(game.id); this.currentBet.set(game.bet); - this.gameInProgress.set(game.state === GameState.IN_PROGRESS); - this.gameState.set(game.state as GameState); - - const isGameOver = game.state !== GameState.IN_PROGRESS; + this.gameInProgress.set(game.state === 'IN_PROGRESS'); this.dealerCards.set( game.dealerCards.map((card, index) => ({ ...card, - hidden: !isGameOver && index === 1 && game.state === GameState.IN_PROGRESS, + hidden: index === 1 && game.state === 'IN_PROGRESS', })) ); @@ -79,136 +63,41 @@ export default class BlackjackComponent { hidden: false, })) ); - - 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'); - } } onNewGame(bet: number): void { - this.isActionInProgress.set(true); - this.currentAction.set('Spiel wird gestartet...'); - this.blackjackService.startGame(bet).subscribe({ next: (game) => { this.updateGameState(game); - this.userService.refreshCurrentUser(); - this.isActionInProgress.set(false); }, error: (error) => { console.error('Failed to start game:', error); - this.isActionInProgress.set(false); }, }); } onHit(): void { - if (!this.currentGameId() || this.isActionInProgress()) return; - - this.isActionInProgress.set(true); - this.currentAction.set('Karte wird gezogen...'); + if (!this.currentGameId()) return; this.blackjackService.hit(this.currentGameId()!).subscribe({ next: (game) => { this.updateGameState(game); - if (game.state !== 'IN_PROGRESS') { - this.userService.refreshCurrentUser(); - } - this.isActionInProgress.set(false); }, error: (error) => { console.error('Failed to hit:', error); - this.handleGameError(error); - this.isActionInProgress.set(false); }, }); } onStand(): void { - if (!this.currentGameId() || this.isActionInProgress()) return; - - if (this.gameState() !== GameState.IN_PROGRESS) { - console.log('Cannot stand: game is not in progress'); - return; - } - - this.isActionInProgress.set(true); - this.currentAction.set('Dealer zieht Karten...'); + if (!this.currentGameId()) return; this.blackjackService.stand(this.currentGameId()!).subscribe({ next: (game) => { this.updateGameState(game); - this.userService.refreshCurrentUser(); - this.isActionInProgress.set(false); }, error: (error) => { console.error('Failed to stand:', error); - this.handleGameError(error); - this.isActionInProgress.set(false); - }, - }); - } - - onDoubleDown(): void { - if (!this.currentGameId() || this.isActionInProgress()) return; - - if (this.gameState() !== GameState.IN_PROGRESS || this.playerCards().length !== 2) { - console.log('Cannot double down: game is not in progress or more than 2 cards'); - return; - } - - this.isActionInProgress.set(true); - this.currentAction.set('Einsatz wird verdoppelt...'); - - this.blackjackService.doubleDown(this.currentGameId()!).subscribe({ - next: (game) => { - this.updateGameState(game); - this.userService.refreshCurrentUser(); - this.isActionInProgress.set(false); - }, - error: (error) => { - console.error('Failed to double down:', error); - this.handleGameError(error); - this.isActionInProgress.set(false); - }, - }); - } - - onCloseGameResult(): void { - console.log('Closing game result dialog'); - this.showGameResult.set(false); - } - - private handleGameError(error: HttpErrorResponse): void { - if (error instanceof HttpErrorResponse) { - if (error.status === 400 && error.error?.error === 'Invalid state') { - this.gameInProgress.set(false); - - this.refreshUserBalance(); - } 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(); - - if (this.currentGameId()) { - this.refreshGameState(this.currentGameId()!); - } - } - } - } - - private refreshGameState(gameId: number): void { - this.blackjackService.getGame(gameId).subscribe({ - next: (game) => { - this.updateGameState(game); - }, - error: (err) => { - console.error('Failed to refresh game state:', err); }, }); } diff --git a/frontend/src/app/feature/game/blackjack/components/dealer-hand/dealer-hand.component.ts b/frontend/src/app/feature/game/blackjack/components/dealer-hand/dealer-hand.component.ts index 45174ca..89517a1 100644 --- a/frontend/src/app/feature/game/blackjack/components/dealer-hand/dealer-hand.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/dealer-hand/dealer-hand.component.ts @@ -1,7 +1,7 @@ -import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { Card } from '@blackjack/models/blackjack.model'; import { PlayingCardComponent } from '../playing-card/playing-card.component'; +import { Card } from '../../models/blackjack.model'; @Component({ selector: 'app-dealer-hand', @@ -13,12 +13,11 @@ import { PlayingCardComponent } from '../playing-card/playing-card.component';
@if (cards.length > 0) { - @for (card of cardsWithState; track card.id) { + @for (card of cards; track card) { } } @else { @@ -32,31 +31,6 @@ import { PlayingCardComponent } from '../playing-card/playing-card.component'; `, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class DealerHandComponent implements OnChanges { +export class DealerHandComponent { @Input() cards: Card[] = []; - cardsWithState: (Card & { isNew: boolean; id: string })[] = []; - - private lastCardCount = 0; - - ngOnChanges(changes: SimpleChanges): void { - if (changes['cards']) { - this.updateCardsWithState(); - } - } - - private updateCardsWithState(): void { - const newCards = this.cards.length > this.lastCardCount; - - this.cardsWithState = this.cards.map((card, index) => { - const isNew = newCards && index >= this.lastCardCount; - - return { - ...card, - isNew, - id: `${card.suit}-${card.rank}-${index}`, - }; - }); - - this.lastCardCount = this.cards.length; - } } 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 d01adc6..c78111f 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 @@ -1,100 +1,36 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { GameState } from '@blackjack/enum/gameState'; -import { Card } from '@blackjack/models/blackjack.model'; -import { GameControlsService } from '@blackjack/services/game-controls.service'; @Component({ selector: 'app-game-controls', standalone: true, imports: [CommonModule], template: ` -
-
-
-
- Deine Punkte: {{ gameControlsService.calculateHandValue(playerCards) }} -
-
- Status: - {{ - gameControlsService.getStatusText(gameState) - }} -
-
-
-
- - - - -
+
+ + +
`, changeDetection: ChangeDetectionStrategy.OnPush, }) export class GameControlsComponent { - @Input() playerCards: Card[] = []; - @Input() gameState: GameState = GameState.IN_PROGRESS; - @Input() isActionInProgress = false; - @Output() hit = new EventEmitter(); @Output() stand = new EventEmitter(); - @Output() doubleDown = new EventEmitter(); @Output() leave = new EventEmitter(); - - protected readonly GameState = GameState; - - constructor(protected gameControlsService: GameControlsService) {} } 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..e35bf73 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 @@ -18,6 +18,10 @@ import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angula

Spiel Informationen

+
+ Guthaben: + {{ balance | currency: 'EUR' }} +
Aktuelle Wette: @@ -82,17 +86,10 @@ import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angula
@@ -104,7 +101,6 @@ export class GameInfoComponent implements OnChanges { @Input() balance = 0; @Input() currentBet = 0; @Input() gameInProgress = false; - @Input() isActionInProgress = false; @Output() newGame = new EventEmitter(); betForm: FormGroup; diff --git a/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.ts b/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.ts deleted file mode 100644 index b76baa5..0000000 --- a/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input, Output, EventEmitter } from '@angular/core'; -import { CommonModule, CurrencyPipe } from '@angular/common'; -import { animate, style, transition, trigger } from '@angular/animations'; -import { GameState } from '../../enum/gameState'; - -@Component({ - selector: 'app-game-result', - standalone: true, - imports: [CommonModule, CurrencyPipe], - template: ` - - `, - changeDetection: ChangeDetectionStrategy.OnPush, - animations: [ - trigger('fadeInOut', [ - transition(':enter', [ - style({ opacity: 0 }), - animate('150ms ease-out', style({ opacity: 1 })), - ]), - transition(':leave', [animate('150ms ease-in', style({ opacity: 0 }))]), - ]), - trigger('cardAnimation', [ - transition(':enter', [ - style({ opacity: 0, transform: 'scale(0.95)' }), - animate('200ms ease-out', style({ opacity: 1, transform: 'scale(1)' })), - ]), - ]), - ], -}) -export class GameResultComponent { - @Input() gameState: GameState = GameState.IN_PROGRESS; - @Input() amount = 0; - @Input() set show(value: boolean) { - console.log('GameResultComponent show input changed:', value, 'gameState:', this.gameState); - this.visible = value; - } - - @Output() gameResultClosed = new EventEmitter(); - - visible = false; - - get isWin(): boolean { - return this.gameState === GameState.PLAYER_WON || this.gameState === GameState.PLAYER_BLACKJACK; - } - - get isLoss(): boolean { - return this.gameState === GameState.PLAYER_LOST; - } - - get isDraw(): boolean { - return this.gameState === GameState.DRAW; - } - - getResultTitle(): string { - if (this.gameState === GameState.PLAYER_BLACKJACK) return 'Blackjack!'; - if (this.isWin) return 'Gewonnen!'; - if (this.isLoss) return 'Verloren!'; - if (this.isDraw) return 'Unentschieden!'; - return ''; - } - - getResultMessage(): string { - if (this.gameState === GameState.PLAYER_BLACKJACK) - return 'Glückwunsch! Du hast mit einem Blackjack gewonnen!'; - if (this.isWin) return 'Glückwunsch! Du hast diese Runde gewonnen.'; - if (this.isLoss) return 'Schade! Du hast diese Runde verloren.'; - if (this.isDraw) return 'Diese Runde endet unentschieden. Dein Einsatz wurde zurückgegeben.'; - return ''; - } - - getResultClass(): string { - if (this.gameState === GameState.PLAYER_BLACKJACK) return 'text-emerald font-bold'; - if (this.isWin) return 'text-emerald'; - if (this.isLoss) return 'text-accent-red'; - if (this.isDraw) return 'text-yellow-400'; - return ''; - } - - closeDialog(): void { - this.visible = false; - this.gameResultClosed.emit(); - console.log('Dialog closed by user'); - } -} diff --git a/frontend/src/app/feature/game/blackjack/components/player-hand/player-hand.component.ts b/frontend/src/app/feature/game/blackjack/components/player-hand/player-hand.component.ts index b921a3c..e158f67 100644 --- a/frontend/src/app/feature/game/blackjack/components/player-hand/player-hand.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/player-hand/player-hand.component.ts @@ -1,7 +1,7 @@ -import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { CommonModule } from '@angular/common'; import { PlayingCardComponent } from '../playing-card/playing-card.component'; -import { Card } from '@blackjack/models/blackjack.model'; +import { Card } from '../../models/blackjack.model'; @Component({ selector: 'app-player-hand', @@ -15,12 +15,11 @@ import { Card } from '@blackjack/models/blackjack.model'; class="flex justify-center gap-4 min-h-[160px] p-4 border-2 border-emerald-400 rounded-lg" > @if (cards.length > 0) { - @for (card of cardsWithState; track card.id) { + @for (card of cards; track card) { } } @else { @@ -34,31 +33,6 @@ import { Card } from '@blackjack/models/blackjack.model'; `, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class PlayerHandComponent implements OnChanges { +export class PlayerHandComponent { @Input() cards: Card[] = []; - cardsWithState: (Card & { isNew: boolean; id: string })[] = []; - - private lastCardCount = 0; - - ngOnChanges(changes: SimpleChanges): void { - if (changes['cards']) { - this.updateCardsWithState(); - } - } - - private updateCardsWithState(): void { - const newCards = this.cards.length > this.lastCardCount; - - this.cardsWithState = this.cards.map((card, index) => { - const isNew = newCards && index >= this.lastCardCount; - - return { - ...card, - isNew, - id: `${card.suit}-${card.rank}-${index}`, - }; - }); - - this.lastCardCount = this.cards.length; - } } diff --git a/frontend/src/app/feature/game/blackjack/components/playing-card/playing-card.component.ts b/frontend/src/app/feature/game/blackjack/components/playing-card/playing-card.component.ts index 8ae8824..186ac9b 100644 --- a/frontend/src/app/feature/game/blackjack/components/playing-card/playing-card.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/playing-card/playing-card.component.ts @@ -1,15 +1,6 @@ -import { - ChangeDetectionStrategy, - Component, - Input, - AfterViewInit, - ElementRef, - OnChanges, - SimpleChanges, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { gsap } from 'gsap'; -import { Suit, suitSymbols } from '@blackjack/models/blackjack.model'; +import { suitSymbols, Suit } from '../../models/blackjack.model'; @Component({ selector: 'app-playing-card', @@ -17,96 +8,31 @@ import { Suit, suitSymbols } from '@blackjack/models/blackjack.model'; imports: [CommonModule], template: `
@if (!hidden) { - {{ - getDisplayRank(rank) - }} + {{ getDisplayRank(rank) }} } @if (!hidden) { {{ getSuitSymbol(suit) }} } @if (!hidden) { - {{ getDisplayRank(rank) }} + {{ + getDisplayRank(rank) + }} }
`, - styles: [ - ` - .card-element { - transform-style: preserve-3d; - backface-visibility: hidden; - } - `, - ], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class PlayingCardComponent implements AfterViewInit, OnChanges { +export class PlayingCardComponent { @Input({ required: true }) rank!: string; @Input({ required: true }) suit!: Suit; @Input({ required: true }) hidden!: boolean; - @Input() isNew = false; - - constructor(private elementRef: ElementRef) {} - - get isRedSuit(): boolean { - return this.suit === 'HEARTS' || this.suit === 'DIAMONDS'; - } - - ngAfterViewInit(): void { - if (this.isNew) { - this.animateNewCard(); - } - } - - ngOnChanges(changes: SimpleChanges): void { - if (changes['hidden'] && !changes['hidden'].firstChange) { - this.animateCardFlip(); - } - } - - private animateNewCard(): void { - const cardElement = this.elementRef.nativeElement.querySelector('.card-element'); - gsap.fromTo( - cardElement, - { - y: -100, - opacity: 0, - rotation: -10, - scale: 0.7, - }, - { - y: 0, - opacity: 1, - rotation: 0, - scale: 1, - duration: 0.5, - ease: 'power2.out', - } - ); - } - - private animateCardFlip(): void { - const cardElement = this.elementRef.nativeElement.querySelector('.card-element'); - gsap.to(cardElement, { - rotationY: 180, - duration: 0.3, - onComplete: () => { - gsap.set(cardElement, { rotationY: 0 }); - }, - }); - } protected getSuitSymbol(suit: Suit): string { return suitSymbols[suit]; diff --git a/frontend/src/app/feature/game/blackjack/enum/gameState.ts b/frontend/src/app/feature/game/blackjack/enum/gameState.ts deleted file mode 100644 index 977e16d..0000000 --- a/frontend/src/app/feature/game/blackjack/enum/gameState.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum GameState { - PLAYER_WON = 'PLAYER_WON', - IN_PROGRESS = 'IN_PROGRESS', - PLAYER_LOST = 'PLAYER_LOST', - DRAW = 'DRAW', - PLAYER_BLACKJACK = 'PLAYER_BLACKJACK', -} diff --git a/frontend/src/app/feature/game/blackjack/services/blackjack.service.ts b/frontend/src/app/feature/game/blackjack/services/blackjack.service.ts index ca3f218..86ab7cf 100644 --- a/frontend/src/app/feature/game/blackjack/services/blackjack.service.ts +++ b/frontend/src/app/feature/game/blackjack/services/blackjack.service.ts @@ -1,7 +1,7 @@ import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, catchError } from 'rxjs'; -import { BlackjackGame } from '@blackjack/models/blackjack.model'; +import { BlackjackGame } from '../models/blackjack.model'; @Injectable({ providedIn: 'root', @@ -41,26 +41,4 @@ export class BlackjackService { }) ); } - - doubleDown(gameId: number): Observable { - return this.http - .post(`/backend/blackjack/${gameId}/doubleDown`, {}, { responseType: 'json' }) - .pipe( - catchError((error) => { - console.error('Double Down error:', error); - throw error; - }) - ); - } - - getGame(gameId: number): Observable { - return this.http - .get(`/backend/blackjack/${gameId}`, { responseType: 'json' }) - .pipe( - catchError((error) => { - console.error('Get game error:', error); - throw error; - }) - ); - } } diff --git a/frontend/src/app/feature/game/blackjack/services/game-controls.service.ts b/frontend/src/app/feature/game/blackjack/services/game-controls.service.ts deleted file mode 100644 index fd055c8..0000000 --- a/frontend/src/app/feature/game/blackjack/services/game-controls.service.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Card } from '../models/blackjack.model'; -import { GameState } from '../enum/gameState'; - -@Injectable({ - providedIn: 'root', -}) -export class GameControlsService { - calculateHandValue(cards: Card[]): number { - let sum = 0; - let aceCount = 0; - - const rankValues: Record = { - TWO: 2, - THREE: 3, - FOUR: 4, - FIVE: 5, - SIX: 6, - SEVEN: 7, - EIGHT: 8, - NINE: 9, - TEN: 10, - JACK: 10, - QUEEN: 10, - KING: 10, - ACE: 11, - }; - - for (const card of cards) { - if (!card.hidden) { - const value = rankValues[card.rank] || 0; - sum += value; - if (card.rank === 'ACE') { - aceCount++; - } - } - } - - while (sum > 21 && aceCount > 0) { - sum -= 10; - aceCount--; - } - - return sum; - } - - getStatusText(state: GameState): string { - switch (state) { - case GameState.IN_PROGRESS: - return 'Spiel läuft'; - case GameState.PLAYER_WON: - return 'Gewonnen!'; - case GameState.PLAYER_LOST: - return 'Verloren!'; - case GameState.DRAW: - return 'Unentschieden!'; - default: - return state; - } - } - - getStatusClass(state: GameState): string { - switch (state) { - case GameState.PLAYER_WON: - return 'text-emerald'; - case GameState.PLAYER_LOST: - return 'text-accent-red'; - case GameState.DRAW: - return 'text-yellow-400'; - default: - return 'text-white'; - } - } -} diff --git a/frontend/src/app/feature/home/home.component.ts b/frontend/src/app/feature/home/home.component.ts index eb12454..b3237ee 100644 --- a/frontend/src/app/feature/home/home.component.ts +++ b/frontend/src/app/feature/home/home.component.ts @@ -1,11 +1,11 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { NavbarComponent } from '../../shared/components/navbar/navbar.component'; import { CurrencyPipe, NgFor } from '@angular/common'; +import { Game } from '../../model/Game'; +import { Transaction } from '../../model/Transaction'; import { DepositComponent } from '../deposit/deposit.component'; +import { ConfirmationComponent } from '../../shared/components/confirmation/confirmation.component'; import { ActivatedRoute, Router } from '@angular/router'; -import { ConfirmationComponent } from '@shared/components/confirmation/confirmation.component'; -import { Transaction } from 'app/model/Transaction'; -import { NavbarComponent } from '@shared/components/navbar/navbar.component'; -import { Game } from 'app/model/Game'; @Component({ selector: 'app-homepage', diff --git a/frontend/src/app/feature/landing/landing.component.ts b/frontend/src/app/feature/landing/landing.component.ts index 62fa25e..7fa92b6 100644 --- a/frontend/src/app/feature/landing/landing.component.ts +++ b/frontend/src/app/feature/landing/landing.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, OnInit, OnDestroy } from '@angular/core'; +import { NavbarComponent } from '../../shared/components/navbar/navbar.component'; import { NgFor } from '@angular/common'; -import { NavbarComponent } from '@shared/components/navbar/navbar.component'; @Component({ selector: 'app-landing-page', diff --git a/frontend/src/app/feature/login-success/login-success.component.ts b/frontend/src/app/feature/login-success/login-success.component.ts index 3b506ca..067afe6 100644 --- a/frontend/src/app/feature/login-success/login-success.component.ts +++ b/frontend/src/app/feature/login-success/login-success.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; +import { UserService } from '../../service/user.service'; import { KeycloakService } from 'keycloak-angular'; import { Router } from '@angular/router'; -import { UserService } from '@service/user.service'; @Component({ selector: 'app-login-success', diff --git a/frontend/src/app/service/user.service.ts b/frontend/src/app/service/user.service.ts index 2ad53ce..20573ff 100644 --- a/frontend/src/app/service/user.service.ts +++ b/frontend/src/app/service/user.service.ts @@ -1,7 +1,7 @@ import { inject, Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { KeycloakProfile } from 'keycloak-js'; -import { BehaviorSubject, catchError, EMPTY, Observable, tap } from 'rxjs'; +import { catchError, EMPTY, Observable } from 'rxjs'; import { User } from '../model/User'; @Injectable({ @@ -9,39 +9,20 @@ import { User } from '../model/User'; }) export class UserService { private http: HttpClient = inject(HttpClient); - private currentUserSubject = new BehaviorSubject(null); - public currentUser$ = this.currentUserSubject.asObservable(); - - constructor() { - // Initialize with current user data - this.getCurrentUser().subscribe(); - } public getUser(id: string): Observable { - return this.http.get(`/backend/user/${id}`).pipe( - catchError(() => EMPTY), - tap((user) => this.currentUserSubject.next(user)) - ); + return this.http.get(`/backend/user/${id}`).pipe(catchError(() => EMPTY)); } public getCurrentUser(): Observable { - return this.http.get('/backend/user').pipe( - catchError(() => EMPTY), - tap((user) => this.currentUserSubject.next(user)) - ); - } - - public refreshCurrentUser(): void { - this.getCurrentUser().subscribe(); + return this.http.get('/backend/user').pipe(catchError(() => EMPTY)); } public createUser(id: string, username: string): Observable { - return this.http - .post('/backend/user', { - keycloakId: id, - username: username, - }) - .pipe(tap((user) => this.currentUserSubject.next(user))); + return this.http.post('/backend/user', { + keycloakId: id, + username: username, + }); } public async getOrCreateUser(userProfile: KeycloakProfile) { diff --git a/frontend/src/app/shared/components/confirmation/confirmation.component.ts b/frontend/src/app/shared/components/confirmation/confirmation.component.ts index 8bc884a..9c26f22 100644 --- a/frontend/src/app/shared/components/confirmation/confirmation.component.ts +++ b/frontend/src/app/shared/components/confirmation/confirmation.component.ts @@ -8,7 +8,7 @@ import { AfterViewInit, OnDestroy, } from '@angular/core'; -import { ModalAnimationService } from '@shared/services/modal-animation.service'; +import { ModalAnimationService } from '../../services/modal-animation.service'; import gsap from 'gsap'; @Component({ diff --git a/frontend/src/app/shared/components/navbar/navbar.component.html b/frontend/src/app/shared/components/navbar/navbar.component.html index 040e7cf..ee95fa2 100644 --- a/frontend/src/app/shared/components/navbar/navbar.component.html +++ b/frontend/src/app/shared/components/navbar/navbar.component.html @@ -18,7 +18,7 @@
- {{ balance() | currency: 'EUR' : 'symbol' : '1.2-2' }} + Guthaben: {{ balance() | currency: 'EUR' : 'symbol' : '1.2-2' }}
} diff --git a/frontend/src/app/shared/components/navbar/navbar.component.ts b/frontend/src/app/shared/components/navbar/navbar.component.ts index 6b972ac..a6bbc15 100644 --- a/frontend/src/app/shared/components/navbar/navbar.component.ts +++ b/frontend/src/app/shared/components/navbar/navbar.component.ts @@ -1,17 +1,9 @@ -import { - ChangeDetectionStrategy, - Component, - inject, - OnInit, - OnDestroy, - signal, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core'; import { RouterModule } from '@angular/router'; import { KeycloakService } from 'keycloak-angular'; -import { CurrencyPipe } from '@angular/common'; -import { UserService } from '@service/user.service'; -import { Subscription } from 'rxjs'; +import { UserService } from '../../../service/user.service'; +import { CurrencyPipe } from '@angular/common'; @Component({ selector: 'app-navbar', templateUrl: './navbar.component.html', @@ -19,27 +11,22 @@ import { Subscription } from 'rxjs'; imports: [RouterModule, CurrencyPipe], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class NavbarComponent implements OnInit, OnDestroy { +export class NavbarComponent implements OnInit { isMenuOpen = false; private keycloakService: KeycloakService = inject(KeycloakService); isLoggedIn = this.keycloakService.isLoggedIn(); private userService = inject(UserService); - private userSubscription: Subscription | undefined; + private user = this.userService.getCurrentUser(); + public balance = signal(0); ngOnInit() { - this.userSubscription = this.userService.currentUser$.subscribe((user) => { + this.user.subscribe((user) => { this.balance.set(user?.balance ?? 0); }); } - ngOnDestroy() { - if (this.userSubscription) { - this.userSubscription.unsubscribe(); - } - } - login() { try { const baseUrl = window.location.origin; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index faf0f24..a8bb65b 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -3,13 +3,6 @@ { "compileOnSave": false, "compilerOptions": { - "baseUrl": "./src", - "paths": { - "@service/*": ["app/service/*"], - "@environments/*": ["environments/*"], - "@shared/*": ["app/shared/*"], - "@blackjack/*": ["app/feature/game/blackjack/*"] - }, "outDir": "./dist/out-tsc", "strict": true, "noImplicitOverride": true,