Compare commits

..

No commits in common. "v1.17.0" and "v1.16.0" have entirely different histories.

9 changed files with 54 additions and 320 deletions

View file

@ -23,7 +23,6 @@ public class BlackJackService {
BlackJackGameEntity game = new BlackJackGameEntity(); BlackJackGameEntity game = new BlackJackGameEntity();
game.setUser(user); game.setUser(user);
game.setBet(betAmount); game.setBet(betAmount);
game.setState("IN_PROGRESS");
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
CardEntity playerCard = createRandomCard(game); CardEntity playerCard = createRandomCard(game);

View file

@ -3,23 +3,20 @@
<div class="container mx-auto px-4 py-6 space-y-8"> <div class="container mx-auto px-4 py-6 space-y-8">
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6"> <div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
<div class="lg:col-span-3 space-y-6 flex flex-col gap-4"> <div class="lg:col-span-3 space-y-6 flex flex-col gap-4">
<app-dealer-hand [cards]="dealerCards()"></app-dealer-hand> <app-dealer-hand [cards]="dealerCards"></app-dealer-hand>
<app-player-hand [cards]="playerCards()"></app-player-hand> <app-player-hand [cards]="playerCards"></app-player-hand>
@if (gameInProgress()) {
<app-game-controls <app-game-controls
(hit)="onHit()" (hit)="onHit()"
(stand)="onStand()" (stand)="onStand()"
(leave)="leaveGame()" (leave)="leaveGame()"
></app-game-controls> ></app-game-controls>
}
</div> </div>
<div class="lg:col-span-1 space-y-6"> <div class="lg:col-span-1 space-y-6">
<app-game-info <app-game-info
[balance]="balance()" [balance]="balance()"
[currentBet]="currentBet()" [currentBet]="currentBet"
[gameInProgress]="gameInProgress()" (newGame)="onNewGame()"
(newGame)="onNewGame($event)"
></app-game-info> ></app-game-info>
</div> </div>
</div> </div>

View file

@ -8,8 +8,7 @@ import { DealerHandComponent } from './components/dealer-hand/dealer-hand.compon
import { PlayerHandComponent } from './components/player-hand/player-hand.component'; import { PlayerHandComponent } from './components/player-hand/player-hand.component';
import { GameControlsComponent } from './components/game-controls/game-controls.component'; import { GameControlsComponent } from './components/game-controls/game-controls.component';
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 } from './models/card.model';
import { BlackjackService } from './services/blackjack.service';
@Component({ @Component({
selector: 'app-blackjack', selector: 'app-blackjack',
@ -29,14 +28,16 @@ import { BlackjackService } from './services/blackjack.service';
export default class BlackjackComponent { export default class BlackjackComponent {
private router = inject(Router); private router = inject(Router);
private userService = inject(UserService); private userService = inject(UserService);
private blackjackService = inject(BlackjackService);
dealerCards = signal<Card[]>([]); dealerCards: Card[] = [
playerCards = signal<Card[]>([]); { value: '2', suit: '♥', hidden: false },
currentBet = signal(0); { value: '3', suit: '♦', hidden: false },
{ value: 'B', suit: '□', hidden: true },
];
playerCards: Card[] = [];
currentBet = 0;
balance = signal(0); balance = signal(0);
currentGameId = signal<number | undefined>(undefined);
gameInProgress = signal(false);
constructor() { constructor() {
this.userService.getCurrentUser().subscribe((user) => { this.userService.getCurrentUser().subscribe((user) => {
@ -44,65 +45,19 @@ export default class BlackjackComponent {
}); });
} }
private updateGameState(game: BlackjackGame) {
console.log('Game state update:', game);
this.currentGameId.set(game.id);
this.currentBet.set(game.bet);
this.gameInProgress.set(game.state === 'IN_PROGRESS');
this.dealerCards.set(
game.dealerCards.map((card, index) => ({
...card,
hidden: index === 1 && game.state === 'IN_PROGRESS',
}))
);
this.playerCards.set(
game.playerCards.map((card) => ({
...card,
hidden: false,
}))
);
}
onNewGame(bet: number): void {
this.blackjackService.startGame(bet).subscribe({
next: (game) => {
this.updateGameState(game);
},
error: (error) => {
console.error('Failed to start game:', error);
},
});
}
onHit(): void { onHit(): void {
if (!this.currentGameId()) return; // Implementation for hit action
this.blackjackService.hit(this.currentGameId()!).subscribe({
next: (game) => {
this.updateGameState(game);
},
error: (error) => {
console.error('Failed to hit:', error);
},
});
} }
onStand(): void { onStand(): void {
if (!this.currentGameId()) return; // Implementation for stand action
this.blackjackService.stand(this.currentGameId()!).subscribe({
next: (game) => {
this.updateGameState(game);
},
error: (error) => {
console.error('Failed to stand:', error);
},
});
} }
leaveGame(): void { leaveGame(): void {
this.router.navigate(['/home']); this.router.navigate(['/home']);
} }
onNewGame(): void {
// Implementation for new game
}
} }

View file

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { PlayingCardComponent } from '../playing-card/playing-card.component'; import { PlayingCardComponent } from '../playing-card/playing-card.component';
import { Card } from '../../models/blackjack.model'; import { Card } from '../../models/card.model';
@Component({ @Component({
selector: 'app-dealer-hand', selector: 'app-dealer-hand',
@ -12,19 +12,12 @@ import { Card } from '../../models/blackjack.model';
<h3 class="section-heading text-2xl mb-4">Croupier's Karten</h3> <h3 class="section-heading text-2xl mb-4">Croupier's Karten</h3>
<div class="card p-6 !bg-accent-red"> <div class="card p-6 !bg-accent-red">
<div class="flex justify-center gap-4 min-h-[160px] p-4 border-2 border-red-400 rounded-lg"> <div class="flex justify-center gap-4 min-h-[160px] p-4 border-2 border-red-400 rounded-lg">
@if (cards.length > 0) {
@for (card of cards; track card) {
<app-playing-card <app-playing-card
[rank]="card.rank" *ngFor="let card of cards"
[value]="card.value"
[suit]="card.suit" [suit]="card.suit"
[hidden]="card.hidden" [hidden]="card.hidden"
></app-playing-card> ></app-playing-card>
}
} @else {
<div class="flex items-center justify-center text-white/70 text-lg font-medium">
Warte auf Spielstart...
</div>
}
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,19 +1,10 @@
import { import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnChanges,
Output,
SimpleChanges,
} from '@angular/core';
import { CommonModule, CurrencyPipe } from '@angular/common'; import { CommonModule, CurrencyPipe } from '@angular/common';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
@Component({ @Component({
selector: 'app-game-info', selector: 'app-game-info',
standalone: true, standalone: true,
imports: [CommonModule, CurrencyPipe, ReactiveFormsModule], imports: [CommonModule, CurrencyPipe],
template: ` template: `
<div class="card p-4"> <div class="card p-4">
<h3 class="section-heading text-xl mb-4">Spiel Informationen</h3> <h3 class="section-heading text-xl mb-4">Spiel Informationen</h3>
@ -28,112 +19,14 @@ import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angula
{{ currentBet | currency: 'EUR' }} {{ currentBet | currency: 'EUR' }}
</span> </span>
</div> </div>
<button class="button-primary w-full py-2" (click)="newGame.emit()">Neues Spiel</button>
@if (!gameInProgress) {
<div class="grid grid-cols-2 gap-2 mb-4">
<button
(click)="setBetAmount(0.1)"
class="button-primary py-2 text-sm"
[disabled]="gameInProgress"
>
10%
</button>
<button
(click)="setBetAmount(0.25)"
class="button-primary py-2 text-sm"
[disabled]="gameInProgress"
>
25%
</button>
<button
(click)="setBetAmount(0.5)"
class="button-primary py-2 text-sm"
[disabled]="gameInProgress"
>
50%
</button>
<button
(click)="setBetAmount(1)"
class="button-primary py-2 text-sm"
[disabled]="gameInProgress"
>
100%
</button>
</div>
}
<form [formGroup]="betForm" (ngSubmit)="onSubmit()" class="space-y-2">
<div class="space-y-1">
<label for="bet" class="text-sm text-text-secondary">Einsatz</label>
<input
type="number"
id="bet"
formControlName="bet"
class="w-full px-3 py-2 bg-deep-blue-light text-white rounded focus:outline-none focus:ring-2 focus:ring-emerald"
[min]="1"
[max]="balance"
step="0.01"
/>
@if (betForm.get('bet')?.errors?.['required'] && betForm.get('bet')?.touched) {
<span class="text-xs text-accent-red">Bitte geben Sie einen Einsatz ein</span>
}
@if (betForm.get('bet')?.errors?.['min'] && betForm.get('bet')?.touched) {
<span class="text-xs text-accent-red">Mindestens 1 setzen</span>
}
@if (betForm.get('bet')?.errors?.['max'] && betForm.get('bet')?.touched) {
<span class="text-xs text-accent-red">Nicht genügend Guthaben</span>
}
</div>
<button
type="submit"
class="button-primary w-full py-2"
[disabled]="!betForm.valid || gameInProgress"
>
Neues Spiel
</button>
</form>
</div> </div>
</div> </div>
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class GameInfoComponent implements OnChanges { export class GameInfoComponent {
@Input() balance = 0; @Input() balance = 0;
@Input() currentBet = 0; @Input() currentBet = 0;
@Input() gameInProgress = false; @Output() newGame = new EventEmitter<void>();
@Output() newGame = new EventEmitter<number>();
betForm: FormGroup;
constructor(private fb: FormBuilder) {
this.betForm = this.fb.group({
bet: ['', [Validators.required, Validators.min(1)]],
});
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['balance']) {
this.betForm
.get('bet')
?.setValidators([Validators.required, Validators.min(1), Validators.max(this.balance)]);
this.betForm.get('bet')?.updateValueAndValidity();
}
}
setBetAmount(percentage: number) {
const betAmount = Math.floor(this.balance * percentage * 100) / 100;
if (betAmount >= 1) {
this.betForm.patchValue({ bet: betAmount });
}
}
onSubmit() {
if (this.betForm.valid) {
const betAmount = parseFloat(this.betForm.value.bet);
if (betAmount <= this.balance) {
this.newGame.emit(betAmount);
this.betForm.reset();
}
}
}
} }

View file

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { PlayingCardComponent } from '../playing-card/playing-card.component'; import { PlayingCardComponent } from '../playing-card/playing-card.component';
import { Card } from '../../models/blackjack.model'; import { Card } from '../../models/card.model';
@Component({ @Component({
selector: 'app-player-hand', selector: 'app-player-hand',
@ -14,19 +14,12 @@ import { Card } from '../../models/blackjack.model';
<div <div
class="flex justify-center gap-4 min-h-[160px] p-4 border-2 border-emerald-400 rounded-lg" 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 cards; track card) {
<app-playing-card <app-playing-card
[rank]="card.rank" *ngFor="let card of cards"
[value]="card.value"
[suit]="card.suit" [suit]="card.suit"
[hidden]="card.hidden" [hidden]="card.hidden"
></app-playing-card> ></app-playing-card>
}
} @else {
<div class="flex items-center justify-center text-white/70 text-lg font-medium">
Platziere eine Wette um zu spielen...
</div>
}
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,6 +1,5 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { suitSymbols, Suit } from '../../models/blackjack.model';
@Component({ @Component({
selector: 'app-playing-card', selector: 'app-playing-card',
@ -11,49 +10,21 @@ import { suitSymbols, Suit } from '../../models/blackjack.model';
class="w-24 h-36 rounded-lg p-2 relative flex flex-col justify-between shadow-lg" class="w-24 h-36 rounded-lg p-2 relative flex flex-col justify-between shadow-lg"
[class]="hidden ? 'bg-red-800' : 'bg-white'" [class]="hidden ? 'bg-red-800' : 'bg-white'"
> >
@if (!hidden) { <span *ngIf="!hidden" class="text-xl font-bold text-accent-red">{{ value }}</span>
<span class="text-xl font-bold text-accent-red">{{ getDisplayRank(rank) }}</span>
}
@if (!hidden) {
<span <span
*ngIf="!hidden"
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-3xl text-accent-red" class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-3xl text-accent-red"
>{{ getSuitSymbol(suit) }}</span >{{ suit }}</span
> >
} <span *ngIf="!hidden" class="text-xl font-bold text-accent-red self-end rotate-180">{{
@if (!hidden) { value
<span class="text-xl font-bold text-accent-red self-end rotate-180">{{
getDisplayRank(rank)
}}</span> }}</span>
}
</div> </div>
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class PlayingCardComponent { export class PlayingCardComponent {
@Input({ required: true }) rank!: string; @Input({ required: true }) value!: string;
@Input({ required: true }) suit!: Suit; @Input({ required: true }) suit!: string;
@Input({ required: true }) hidden!: boolean; @Input({ required: true }) hidden!: boolean;
protected getSuitSymbol(suit: Suit): string {
return suitSymbols[suit];
}
protected getDisplayRank(rank: string): string {
const rankMap: Record<string, string> = {
TWO: '2',
THREE: '3',
FOUR: '4',
FIVE: '5',
SIX: '6',
SEVEN: '7',
EIGHT: '8',
NINE: '9',
TEN: '10',
JACK: 'J',
QUEEN: 'Q',
KING: 'K',
ACE: 'A',
};
return rankMap[rank] || rank;
}
} }

View file

@ -1,23 +0,0 @@
export type Suit = 'HEARTS' | 'DIAMONDS' | 'CLUBS' | 'SPADES';
export interface Card {
suit: Suit;
rank: string;
hidden: boolean;
}
export interface BlackjackGame {
id: number;
state: string;
bet: number;
playerCards: Card[];
dealerCards: Card[];
userId: number;
}
export const suitSymbols: Record<Suit, string> = {
HEARTS: '♥',
DIAMONDS: '♦',
CLUBS: '♣',
SPADES: '♠',
};

View file

@ -1,44 +0,0 @@
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, catchError } from 'rxjs';
import { BlackjackGame } from '../models/blackjack.model';
@Injectable({
providedIn: 'root',
})
export class BlackjackService {
private http = inject(HttpClient);
startGame(bet: number): Observable<BlackjackGame> {
return this.http
.post<BlackjackGame>('/backend/blackjack/start', { betAmount: bet }, { responseType: 'json' })
.pipe(
catchError((error) => {
console.error('Start game error:', error);
throw error;
})
);
}
hit(gameId: number): Observable<BlackjackGame> {
return this.http
.post<BlackjackGame>(`/backend/blackjack/${gameId}/hit`, {}, { responseType: 'json' })
.pipe(
catchError((error) => {
console.error('Hit error:', error);
throw error;
})
);
}
stand(gameId: number): Observable<BlackjackGame> {
return this.http
.post<BlackjackGame>(`/backend/blackjack/${gameId}/stand`, {}, { responseType: 'json' })
.pipe(
catchError((error) => {
console.error('Stand error:', error);
throw error;
})
);
}
}