feat: add stand and get game features to blackjack game

This commit is contained in:
Jan-Marlon Leibl 2025-03-27 15:22:24 +01:00
commit d90fcdcf1e
Signed by: jleibl
GPG key ID: 300B2F906DC6F1D5
13 changed files with 446 additions and 32 deletions

View file

@ -1,36 +1,118 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, 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: `
<div class="flex justify-center gap-4">
<button
(click)="hit.emit()"
class="button-primary px-8 py-4 text-lg font-medium min-w-[120px]"
>
Ziehen
</button>
<button
(click)="stand.emit()"
class="button-primary px-8 py-4 text-lg font-medium min-w-[120px]"
>
Halten
</button>
<button
(click)="leave.emit()"
class="bg-accent-red hover:bg-accent-red/80 px-8 py-4 rounded text-lg font-medium min-w-[120px] transition-all duration-300"
>
Abbrechen
</button>
<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">
<button
(click)="hit.emit()"
class="button-primary px-8 py-4 text-lg font-medium min-w-[120px]"
[disabled]="gameState !== 'IN_PROGRESS'"
>
Ziehen
</button>
<button
(click)="stand.emit()"
class="button-primary px-8 py-4 text-lg font-medium min-w-[120px]"
[disabled]="gameState !== 'IN_PROGRESS'"
>
Halten
</button>
<button
(click)="leave.emit()"
class="bg-accent-red hover:bg-accent-red/80 px-8 py-4 rounded text-lg font-medium min-w-[120px] transition-all duration-300"
>
Abbrechen
</button>
</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GameControlsComponent {
@Input() playerCards: Card[] = [];
@Input() gameState: string = 'IN_PROGRESS';
@Output() hit = new EventEmitter<void>();
@Output() stand = 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';
}
}
}

View file

@ -0,0 +1 @@
/* No custom styles needed */

View file

@ -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();
});
});

View file

@ -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 '';
}
}