diff --git a/backend/requests/blackjack.http b/backend/requests/blackjack.http index 16f7122..1ac2def 100644 --- a/backend/requests/blackjack.http +++ b/backend/requests/blackjack.http @@ -10,11 +10,3 @@ Content-Type: application/json POST http://localhost:8080/blackjack/202/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..58505eb 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,43 +40,15 @@ public class BlackJackGameController { return ResponseEntity.notFound().build(); } + if (game.getState() != BlackJackState.IN_PROGRESS) { + Map 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 stand(@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.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/BlackJackGameEntity.java b/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameEntity.java index 0dbfcc0..eb5a704 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameEntity.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameEntity.java @@ -24,15 +24,12 @@ public class BlackJackGameEntity { @GeneratedValue private Long id; - @Version - @JsonIgnore - private Long version; - @ManyToOne @JoinColumn(name = "user_id", nullable = false) @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 5c39459..df1a459 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,168 +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); - + + 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); + game.setState(getState(game)); - deductBetFromBalance(user, betAmount); - - return blackJackGameRepository.save(game); - } + user.setBalance(user.getBalance().subtract(betAmount)); - @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); - } + 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; } - - 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) { + public BlackJackGameEntity hit(BlackJackGameEntity 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); - } - } - - 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); // For draw, player gets their bet back - } - } - - 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 = 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) { @@ -216,7 +99,6 @@ 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/backend/src/main/java/de/szut/casino/blackjack/BlackJackState.java b/backend/src/main/java/de/szut/casino/blackjack/BlackJackState.java index e1d422d..2ba802c 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackState.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackState.java @@ -4,5 +4,4 @@ public enum BlackJackState { PLAYER_WON, IN_PROGRESS, PLAYER_LOST, - DRAW, } diff --git a/backend/src/main/java/de/szut/casino/blackjack/CardEntity.java b/backend/src/main/java/de/szut/casino/blackjack/CardEntity.java index 27d09d6..5520b58 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/CardEntity.java +++ b/backend/src/main/java/de/szut/casino/blackjack/CardEntity.java @@ -19,10 +19,6 @@ public class CardEntity { @JsonIgnore private Long id; - @Version - @JsonIgnore - private Long version; - @ManyToOne @JoinColumn(name = "game_id", nullable = false) @JsonBackReference diff --git a/frontend/src/app/feature/game/blackjack/blackjack.component.html b/frontend/src/app/feature/game/blackjack/blackjack.component.html index 62d5489..ae130d4 100644 --- a/frontend/src/app/feature/game/blackjack/blackjack.component.html +++ b/frontend/src/app/feature/game/blackjack/blackjack.component.html @@ -5,25 +5,10 @@
- - - @if (isActionInProgress()) { -
-
-
- {{ currentAction() }} -
-
- } - @if (gameInProgress()) { } @@ -34,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 5b75bd6..174c358 100644 --- a/frontend/src/app/feature/game/blackjack/blackjack.component.ts +++ b/frontend/src/app/feature/game/blackjack/blackjack.component.ts @@ -10,8 +10,6 @@ import { GameControlsComponent } from './components/game-controls/game-controls. import { GameInfoComponent } from './components/game-info/game-info.component'; import { Card, BlackjackGame } from './models/blackjack.model'; import { BlackjackService } from './services/blackjack.service'; -import { HttpErrorResponse } from '@angular/common/http'; -import { GameResultComponent } from './components/game-result/game-result.component'; @Component({ selector: 'app-blackjack', @@ -24,7 +22,6 @@ import { GameResultComponent } from './components/game-result/game-result.compon PlayerHandComponent, GameControlsComponent, GameInfoComponent, - GameResultComponent, ], templateUrl: './blackjack.component.html', changeDetection: ChangeDetectionStrategy.OnPush, @@ -40,18 +37,8 @@ export default class BlackjackComponent { balance = signal(0); currentGameId = signal(undefined); gameInProgress = signal(false); - gameState = signal('IN_PROGRESS'); - showGameResult = signal(false); - - // Add loading state trackers - isActionInProgress = signal(false); - currentAction = signal(''); constructor() { - this.refreshUserBalance(); - } - - private refreshUserBalance(): void { this.userService.getCurrentUser().subscribe((user) => { this.balance.set(user?.balance ?? 0); }); @@ -62,15 +49,11 @@ export default class BlackjackComponent { this.currentGameId.set(game.id); this.currentBet.set(game.bet); 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: !isGameOver && index === 1 && game.state === 'IN_PROGRESS', + hidden: index === 1 && game.state === 'IN_PROGRESS', })) ); @@ -80,139 +63,45 @@ export default class BlackjackComponent { hidden: false, })) ); - - // Only refresh and show game result if the game has ended - if (isGameOver) { - console.log('Game is over, state:', game.state); - this.refreshUserBalance(); - - // Show result immediately without resetting first - 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.refreshUserBalance(); - 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; + if (!this.currentGameId()) return; - this.isActionInProgress.set(true); - this.currentAction.set('Karte wird gezogen...'); - this.blackjackService.hit(this.currentGameId()!).subscribe({ next: (game) => { this.updateGameState(game); - 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() !== 'IN_PROGRESS') { - console.log('Cannot stand: game is not in progress'); - return; - } + if (!this.currentGameId()) return; - this.isActionInProgress.set(true); - this.currentAction.set('Dealer zieht Karten...'); - this.blackjackService.stand(this.currentGameId()!).subscribe({ next: (game) => { this.updateGameState(game); - 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() !== '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.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: any): 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); - } - }); - } - leaveGame(): void { this.router.navigate(['/home']); } 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 990b3c8..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,4 +1,4 @@ -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 '../../models/blackjack.model'; @@ -13,12 +13,11 @@ import { Card } from '../../models/blackjack.model';
@if (cards.length > 0) { - @for (card of cardsWithState; track card.id) { + @for (card of cards; track card) { } } @else { @@ -32,33 +31,6 @@ import { Card } from '../../models/blackjack.model'; `, 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) => { - // Consider a card new if it's added after the initial state and is the latest card - const isNew = newCards && index >= this.lastCardCount; - - return { - ...card, - isNew, - // Generate a unique ID to help Angular track the cards - 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 899417f..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,147 +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 { Card } from '../../models/blackjack.model'; @Component({ selector: 'app-game-controls', standalone: true, imports: [CommonModule], template: ` -
-
-
-
Deine Punkte: {{ calculateHandValue(playerCards) }}
-
- Status: {{ getStatusText(gameState) }} -
-
-
-
- - - - -
+
+ + +
`, changeDetection: ChangeDetectionStrategy.OnPush, }) export class GameControlsComponent { - @Input() playerCards: Card[] = []; - @Input() gameState: string = 'IN_PROGRESS'; - @Input() isActionInProgress: boolean = false; - @Output() hit = new EventEmitter(); @Output() stand = new EventEmitter(); - @Output() doubleDown = new EventEmitter(); @Output() leave = new EventEmitter(); - - 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: string): string { - switch (state) { - case 'IN_PROGRESS': - return 'Spiel läuft'; - case 'PLAYER_WON': - return 'Gewonnen!'; - case 'PLAYER_LOST': - return 'Verloren!'; - case 'DRAW': - return 'Unentschieden!'; - default: - return state; - } - } - - getStatusClass(state: string): string { - switch (state) { - case 'PLAYER_WON': - return 'text-emerald'; - case 'PLAYER_LOST': - return 'text-accent-red'; - case 'DRAW': - return 'text-yellow-400'; - default: - return 'text-white'; - } - } } 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 f6b6091..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 @@ -86,15 +86,10 @@ import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angula
@@ -106,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.css b/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.css deleted file mode 100644 index 5c8977f..0000000 --- a/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.css +++ /dev/null @@ -1 +0,0 @@ -/* No custom styles needed */ diff --git a/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.spec.ts b/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.spec.ts deleted file mode 100644 index fbc1d3d..0000000 --- a/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { GameResultComponent } from './game-result.component'; - -describe('GameResultComponent', () => { - let component: GameResultComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [GameResultComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(GameResultComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); 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 48021c7..0000000 --- a/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.ts +++ /dev/null @@ -1,134 +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'; - -@Component({ - selector: 'app-game-result', - standalone: true, - imports: [CommonModule, CurrencyPipe], - template: ` - - `, - styleUrls: ['./game-result.component.css'], - 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: string = ''; - @Input() amount: number = 0; - @Input() set show(value: boolean) { - console.log('GameResultComponent show input changed:', value, 'gameState:', this.gameState); - this.visible = value; - } - - @Output() close = new EventEmitter(); - - visible = false; - - get isWin(): boolean { - return this.gameState === 'PLAYER_WON'; - } - - get isLoss(): boolean { - return this.gameState === 'PLAYER_LOST'; - } - - get isDraw(): boolean { - return this.gameState === 'DRAW'; - } - - getResultTitle(): string { - if (this.isWin) return 'Gewonnen!'; - if (this.isLoss) return 'Verloren!'; - if (this.isDraw) return 'Unentschieden!'; - return ''; - } - - getResultMessage(): string { - 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.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.close.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 2b27fee..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,4 +1,4 @@ -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 '../../models/blackjack.model'; @@ -15,12 +15,11 @@ import { Card } from '../../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,33 +33,6 @@ import { Card } from '../../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) => { - // Consider a card new if it's added after the initial state and is the latest card - const isNew = newCards && index >= this.lastCardCount; - - return { - ...card, - isNew, - // Generate a unique ID to help Angular track the cards - 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 e006bd4..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,7 +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 { suitSymbols, Suit } from '../../models/blackjack.model'; -import { gsap } from 'gsap'; @Component({ selector: 'app-playing-card', @@ -9,90 +8,31 @@ import { gsap } from 'gsap'; imports: [CommonModule], template: `
@if (!hidden) { - {{ getDisplayRank(rank) }} + {{ getDisplayRank(rank) }} } @if (!hidden) { {{ getSuitSymbol(suit) }} } @if (!hidden) { - {{ + {{ 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: boolean = 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/services/blackjack.service.ts b/frontend/src/app/feature/game/blackjack/services/blackjack.service.ts index eecaf45..86ab7cf 100644 --- a/frontend/src/app/feature/game/blackjack/services/blackjack.service.ts +++ b/frontend/src/app/feature/game/blackjack/services/blackjack.service.ts @@ -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; - }) - ); - } }