feat: add stand and get game features to blackjack game
This commit is contained in:
parent
4fa7b63b04
commit
68ea66d4f9
13 changed files with 444 additions and 29 deletions
|
@ -10,3 +10,11 @@ Content-Type: application/json
|
||||||
POST http://localhost:8080/blackjack/202/hit
|
POST http://localhost:8080/blackjack/202/hit
|
||||||
Authorization: Bearer {{token}}
|
Authorization: Bearer {{token}}
|
||||||
|
|
||||||
|
###
|
||||||
|
POST http://localhost:8080/blackjack/202/stand
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
|
||||||
|
###
|
||||||
|
GET http://localhost:8080/blackjack/202
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,23 @@ public class BlackJackGameController {
|
||||||
this.userService = userService;
|
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")
|
@PostMapping("/blackjack/{id}/hit")
|
||||||
public ResponseEntity<Object> hit(@PathVariable Long id, @RequestHeader("Authorization") String token) {
|
public ResponseEntity<Object> hit(@PathVariable Long id, @RequestHeader("Authorization") String token) {
|
||||||
Optional<UserEntity> optionalUser = userService.getCurrentUser(token);
|
Optional<UserEntity> optionalUser = userService.getCurrentUser(token);
|
||||||
|
@ -40,13 +57,24 @@ public class BlackJackGameController {
|
||||||
return ResponseEntity.notFound().build();
|
return ResponseEntity.notFound().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.getState() != BlackJackState.IN_PROGRESS) {
|
return ResponseEntity.ok(blackJackService.hit(game));
|
||||||
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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/start")
|
@PostMapping("/blackjack/start")
|
||||||
|
|
|
@ -24,12 +24,15 @@ public class BlackJackGameEntity {
|
||||||
@GeneratedValue
|
@GeneratedValue
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
@Version
|
||||||
|
@JsonIgnore
|
||||||
|
private Long version;
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "user_id", nullable = false)
|
@JoinColumn(name = "user_id", nullable = false)
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private UserEntity user;
|
private UserEntity user;
|
||||||
|
|
||||||
// Expose UserID to JSON output
|
|
||||||
public Long getUserId() {
|
public Long getUserId() {
|
||||||
return user != null ? user.getId() : null;
|
return user != null ? user.getId() : null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package de.szut.casino.blackjack;
|
||||||
import de.szut.casino.user.UserEntity;
|
import de.szut.casino.user.UserEntity;
|
||||||
import de.szut.casino.user.UserRepository;
|
import de.szut.casino.user.UserRepository;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -26,6 +27,7 @@ public class BlackJackService {
|
||||||
return optionalBlackJackGame.orElse(null);
|
return optionalBlackJackGame.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
public BlackJackGameEntity createBlackJackGame(UserEntity user, BigDecimal betAmount) {
|
public BlackJackGameEntity createBlackJackGame(UserEntity user, BigDecimal betAmount) {
|
||||||
BlackJackGameEntity game = new BlackJackGameEntity();
|
BlackJackGameEntity game = new BlackJackGameEntity();
|
||||||
game.setUser(user);
|
game.setUser(user);
|
||||||
|
@ -46,21 +48,79 @@ public class BlackJackService {
|
||||||
user.setBalance(user.getBalance().subtract(betAmount));
|
user.setBalance(user.getBalance().subtract(betAmount));
|
||||||
|
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
blackJackGameRepository.save(game);
|
return blackJackGameRepository.save(game);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public BlackJackGameEntity hit(BlackJackGameEntity game) {
|
||||||
|
game = blackJackGameRepository.findById(game.getId()).orElse(game);
|
||||||
|
|
||||||
|
if (game.getState() != BlackJackState.IN_PROGRESS) {
|
||||||
return game;
|
return game;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlackJackGameEntity hit(BlackJackGameEntity game) {
|
|
||||||
CardEntity drawnCard = drawCardFromDeck(game);
|
CardEntity drawnCard = drawCardFromDeck(game);
|
||||||
drawnCard.setCardType(CardType.PLAYER);
|
drawnCard.setCardType(CardType.PLAYER);
|
||||||
game.getPlayerCards().add(drawnCard);
|
game.getPlayerCards().add(drawnCard);
|
||||||
|
|
||||||
game.setState(getState(game));
|
game.setState(getState(game));
|
||||||
|
|
||||||
|
if (game.getState() == BlackJackState.PLAYER_WON) {
|
||||||
|
updateUserBalance(game, true);
|
||||||
|
} else if (game.getState() == BlackJackState.PLAYER_LOST) {
|
||||||
|
updateUserBalance(game, false);
|
||||||
|
}
|
||||||
|
|
||||||
return blackJackGameRepository.save(game);
|
return blackJackGameRepository.save(game);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public BlackJackGameEntity stand(BlackJackGameEntity game) {
|
||||||
|
game = blackJackGameRepository.findById(game.getId()).orElse(game);
|
||||||
|
|
||||||
|
if (game.getState() != BlackJackState.IN_PROGRESS) {
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (calculateHandValue(game.getDealerCards()) < 17) {
|
||||||
|
CardEntity dealerCard = drawCardFromDeck(game);
|
||||||
|
dealerCard.setCardType(CardType.DEALER);
|
||||||
|
game.getDealerCards().add(dealerCard);
|
||||||
|
}
|
||||||
|
|
||||||
|
int playerValue = calculateHandValue(game.getPlayerCards());
|
||||||
|
int dealerValue = calculateHandValue(game.getDealerCards());
|
||||||
|
|
||||||
|
if (dealerValue > 21 || playerValue > dealerValue) {
|
||||||
|
game.setState(BlackJackState.PLAYER_WON);
|
||||||
|
updateUserBalance(game, true);
|
||||||
|
} else {
|
||||||
|
game.setState(BlackJackState.DRAW);
|
||||||
|
updateUserBalance(game, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return blackJackGameRepository.save(game);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
private void updateUserBalance(BlackJackGameEntity game, boolean isWin) {
|
||||||
|
UserEntity user = game.getUser();
|
||||||
|
user = userRepository.findById(user.getId()).orElse(user);
|
||||||
|
BigDecimal balance = user.getBalance();
|
||||||
|
|
||||||
|
BigDecimal betAmount = game.getBet();
|
||||||
|
|
||||||
|
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) {
|
private void initializeDeck(BlackJackGameEntity game) {
|
||||||
for (Suit suit : Suit.values()) {
|
for (Suit suit : Suit.values()) {
|
||||||
for (Rank rank : Rank.values()) {
|
for (Rank rank : Rank.values()) {
|
||||||
|
|
|
@ -4,4 +4,5 @@ public enum BlackJackState {
|
||||||
PLAYER_WON,
|
PLAYER_WON,
|
||||||
IN_PROGRESS,
|
IN_PROGRESS,
|
||||||
PLAYER_LOST,
|
PLAYER_LOST,
|
||||||
|
DRAW,
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,10 @@ public class CardEntity {
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
@Version
|
||||||
|
@JsonIgnore
|
||||||
|
private Long version;
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "game_id", nullable = false)
|
@JoinColumn(name = "game_id", nullable = false)
|
||||||
@JsonBackReference
|
@JsonBackReference
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
<app-player-hand [cards]="playerCards()"></app-player-hand>
|
<app-player-hand [cards]="playerCards()"></app-player-hand>
|
||||||
@if (gameInProgress()) {
|
@if (gameInProgress()) {
|
||||||
<app-game-controls
|
<app-game-controls
|
||||||
|
[playerCards]="playerCards()"
|
||||||
|
[gameState]="gameState()"
|
||||||
(hit)="onHit()"
|
(hit)="onHit()"
|
||||||
(stand)="onStand()"
|
(stand)="onStand()"
|
||||||
(leave)="leaveGame()"
|
(leave)="leaveGame()"
|
||||||
|
@ -24,3 +26,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Game Result Modal -->
|
||||||
|
<app-game-result
|
||||||
|
[gameState]="gameState()"
|
||||||
|
[amount]="currentBet()"
|
||||||
|
[show]="showGameResult()"
|
||||||
|
></app-game-result>
|
||||||
|
|
|
@ -10,6 +10,8 @@ import { GameControlsComponent } from './components/game-controls/game-controls.
|
||||||
import { GameInfoComponent } from './components/game-info/game-info.component';
|
import { GameInfoComponent } from './components/game-info/game-info.component';
|
||||||
import { Card, BlackjackGame } from './models/blackjack.model';
|
import { Card, BlackjackGame } from './models/blackjack.model';
|
||||||
import { BlackjackService } from './services/blackjack.service';
|
import { BlackjackService } from './services/blackjack.service';
|
||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { GameResultComponent } from './components/game-result/game-result.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-blackjack',
|
selector: 'app-blackjack',
|
||||||
|
@ -22,6 +24,7 @@ import { BlackjackService } from './services/blackjack.service';
|
||||||
PlayerHandComponent,
|
PlayerHandComponent,
|
||||||
GameControlsComponent,
|
GameControlsComponent,
|
||||||
GameInfoComponent,
|
GameInfoComponent,
|
||||||
|
GameResultComponent,
|
||||||
],
|
],
|
||||||
templateUrl: './blackjack.component.html',
|
templateUrl: './blackjack.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
@ -37,8 +40,14 @@ export default class BlackjackComponent {
|
||||||
balance = signal(0);
|
balance = signal(0);
|
||||||
currentGameId = signal<number | undefined>(undefined);
|
currentGameId = signal<number | undefined>(undefined);
|
||||||
gameInProgress = signal(false);
|
gameInProgress = signal(false);
|
||||||
|
gameState = signal<string>('IN_PROGRESS');
|
||||||
|
showGameResult = signal(false);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.refreshUserBalance();
|
||||||
|
}
|
||||||
|
|
||||||
|
private refreshUserBalance(): void {
|
||||||
this.userService.getCurrentUser().subscribe((user) => {
|
this.userService.getCurrentUser().subscribe((user) => {
|
||||||
this.balance.set(user?.balance ?? 0);
|
this.balance.set(user?.balance ?? 0);
|
||||||
});
|
});
|
||||||
|
@ -49,6 +58,7 @@ export default class BlackjackComponent {
|
||||||
this.currentGameId.set(game.id);
|
this.currentGameId.set(game.id);
|
||||||
this.currentBet.set(game.bet);
|
this.currentBet.set(game.bet);
|
||||||
this.gameInProgress.set(game.state === 'IN_PROGRESS');
|
this.gameInProgress.set(game.state === 'IN_PROGRESS');
|
||||||
|
this.gameState.set(game.state);
|
||||||
|
|
||||||
this.dealerCards.set(
|
this.dealerCards.set(
|
||||||
game.dealerCards.map((card, index) => ({
|
game.dealerCards.map((card, index) => ({
|
||||||
|
@ -63,12 +73,20 @@ export default class BlackjackComponent {
|
||||||
hidden: false,
|
hidden: false,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (game.state !== 'IN_PROGRESS') {
|
||||||
|
this.refreshUserBalance();
|
||||||
|
setTimeout(() => {
|
||||||
|
this.showGameResult.set(true);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onNewGame(bet: number): void {
|
onNewGame(bet: number): void {
|
||||||
this.blackjackService.startGame(bet).subscribe({
|
this.blackjackService.startGame(bet).subscribe({
|
||||||
next: (game) => {
|
next: (game) => {
|
||||||
this.updateGameState(game);
|
this.updateGameState(game);
|
||||||
|
this.refreshUserBalance();
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
console.error('Failed to start game:', error);
|
console.error('Failed to start game:', error);
|
||||||
|
@ -85,6 +103,7 @@ export default class BlackjackComponent {
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
console.error('Failed to hit:', error);
|
console.error('Failed to hit:', error);
|
||||||
|
this.handleGameError(error);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -92,16 +111,58 @@ export default class BlackjackComponent {
|
||||||
onStand(): void {
|
onStand(): void {
|
||||||
if (!this.currentGameId()) return;
|
if (!this.currentGameId()) return;
|
||||||
|
|
||||||
|
if (this.gameState() !== 'IN_PROGRESS') {
|
||||||
|
console.log('Cannot stand: game is not in progress');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.blackjackService.stand(this.currentGameId()!).subscribe({
|
this.blackjackService.stand(this.currentGameId()!).subscribe({
|
||||||
next: (game) => {
|
next: (game) => {
|
||||||
this.updateGameState(game);
|
this.updateGameState(game);
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
console.error('Failed to stand:', error);
|
console.error('Failed to stand:', error);
|
||||||
|
this.handleGameError(error);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseGameResult(): void {
|
||||||
|
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 {
|
leaveGame(): void {
|
||||||
this.router.navigate(['/home']);
|
this.router.navigate(['/home']);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,33 @@
|
||||||
import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Card } from '../../models/blackjack.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-game-controls',
|
selector: 'app-game-controls',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule],
|
imports: [CommonModule],
|
||||||
template: `
|
template: `
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="flex justify-center text-lg mb-5">
|
||||||
|
<div class="card p-4">
|
||||||
|
<div class="text-emerald font-bold mb-1">Deine Punkte: {{ calculateHandValue(playerCards) }}</div>
|
||||||
|
<div class="text-text-secondary">
|
||||||
|
Status: <span [class]="getStatusClass(gameState)">{{ getStatusText(gameState) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="flex justify-center gap-4">
|
<div class="flex justify-center gap-4">
|
||||||
<button
|
<button
|
||||||
(click)="hit.emit()"
|
(click)="hit.emit()"
|
||||||
class="button-primary px-8 py-4 text-lg font-medium min-w-[120px]"
|
class="button-primary px-8 py-4 text-lg font-medium min-w-[120px]"
|
||||||
|
[disabled]="gameState !== 'IN_PROGRESS'"
|
||||||
>
|
>
|
||||||
Ziehen
|
Ziehen
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
(click)="stand.emit()"
|
(click)="stand.emit()"
|
||||||
class="button-primary px-8 py-4 text-lg font-medium min-w-[120px]"
|
class="button-primary px-8 py-4 text-lg font-medium min-w-[120px]"
|
||||||
|
[disabled]="gameState !== 'IN_PROGRESS'"
|
||||||
>
|
>
|
||||||
Halten
|
Halten
|
||||||
</button>
|
</button>
|
||||||
|
@ -26,11 +38,81 @@ import { CommonModule } from '@angular/common';
|
||||||
Abbrechen
|
Abbrechen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class GameControlsComponent {
|
export class GameControlsComponent {
|
||||||
|
@Input() playerCards: Card[] = [];
|
||||||
|
@Input() gameState: string = 'IN_PROGRESS';
|
||||||
|
|
||||||
@Output() hit = new EventEmitter<void>();
|
@Output() hit = new EventEmitter<void>();
|
||||||
@Output() stand = new EventEmitter<void>();
|
@Output() stand = new EventEmitter<void>();
|
||||||
@Output() leave = new EventEmitter<void>();
|
@Output() leave = new EventEmitter<void>();
|
||||||
|
|
||||||
|
calculateHandValue(cards: Card[]): number {
|
||||||
|
let sum = 0;
|
||||||
|
let aceCount = 0;
|
||||||
|
|
||||||
|
const rankValues: Record<string, number> = {
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
/* No custom styles needed */
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { GameResultComponent } from './game-result.component';
|
||||||
|
|
||||||
|
describe('GameResultComponent', () => {
|
||||||
|
let component: GameResultComponent;
|
||||||
|
let fixture: ComponentFixture<GameResultComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [GameResultComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(GameResultComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,124 @@
|
||||||
|
import { ChangeDetectionStrategy, Component, Input } 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: `
|
||||||
|
<div
|
||||||
|
*ngIf="visible"
|
||||||
|
[@fadeInOut]
|
||||||
|
class="modal-bg"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="modal-card"
|
||||||
|
[@cardAnimation]
|
||||||
|
>
|
||||||
|
<h2 class="modal-heading" [class]="getResultClass()">{{ getResultTitle() }}</h2>
|
||||||
|
<p class="py-2 text-text-secondary mb-4">{{ getResultMessage() }}</p>
|
||||||
|
|
||||||
|
<div class="bg-deep-blue-light/50 rounded-lg p-5 mb-6 shadow-inner border border-deep-blue-light/30">
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div class="text-text-secondary">Einsatz:</div>
|
||||||
|
<div class="font-medium text-right">{{ amount }} €</div>
|
||||||
|
|
||||||
|
<div class="text-text-secondary">{{ isDraw ? 'Zurückgegeben:' : (isWin ? 'Gewonnen:' : 'Verloren:') }}</div>
|
||||||
|
<div
|
||||||
|
class="font-medium text-right"
|
||||||
|
[ngClass]="{
|
||||||
|
'text-emerald': isWin,
|
||||||
|
'text-accent-red': isLoss,
|
||||||
|
'text-yellow-400': isDraw
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ isLoss ? '-' : '+' }}{{ amount }} €
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-text-secondary border-t border-text-secondary/20 pt-3 font-medium">Gesamt:</div>
|
||||||
|
<div
|
||||||
|
class="font-medium text-right border-t border-text-secondary/20 pt-3"
|
||||||
|
[ngClass]="{
|
||||||
|
'text-emerald': isWin,
|
||||||
|
'text-accent-red': isLoss,
|
||||||
|
'text-white': isDraw
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ isWin ? '+' : (isLoss ? '-' : '') }}{{ amount }} €
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
(click)="visible = false"
|
||||||
|
class="button-primary w-full py-2"
|
||||||
|
>
|
||||||
|
Verstanden
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
styleUrls: ['./game-result.component.css'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
animations: [
|
||||||
|
trigger('fadeInOut', [
|
||||||
|
transition(':enter', [
|
||||||
|
style({ opacity: 0 }),
|
||||||
|
animate('300ms ease-out', style({ opacity: 1 }))
|
||||||
|
]),
|
||||||
|
transition(':leave', [
|
||||||
|
animate('200ms ease-in', style({ opacity: 0 }))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
trigger('cardAnimation', [
|
||||||
|
transition(':enter', [
|
||||||
|
style({ opacity: 0, transform: 'scale(0.8)' }),
|
||||||
|
animate('350ms ease-out', style({ opacity: 1, transform: 'scale(1)' }))
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class GameResultComponent {
|
||||||
|
@Input() gameState: string = '';
|
||||||
|
@Input() amount: number = 0;
|
||||||
|
@Input() set show(value: boolean) {
|
||||||
|
this.visible = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 '';
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,4 +41,15 @@ export class BlackjackService {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGame(gameId: number): Observable<BlackjackGame> {
|
||||||
|
return this.http
|
||||||
|
.get<BlackjackGame>(`/backend/blackjack/${gameId}`, { responseType: 'json' })
|
||||||
|
.pipe(
|
||||||
|
catchError((error) => {
|
||||||
|
console.error('Get game error:', error);
|
||||||
|
throw error;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue