style: format code

This commit is contained in:
Phan Huy Tran 2025-05-21 10:42:36 +02:00 committed by Phan Huy Tran
commit f2aa81b6d2
5 changed files with 274 additions and 221 deletions

View file

@ -63,7 +63,7 @@ export const routes: Routes = [
}, },
{ {
path: 'game/dice', path: 'game/dice',
loadComponent: () => import('./feature/game/dice/dice.component').then(m => m.DiceComponent), loadComponent: () => import('./feature/game/dice/dice.component').then((m) => m.DiceComponent),
canActivate: [authGuard], canActivate: [authGuard],
}, },
]; ];

View file

@ -1,74 +1,121 @@
<div class="container mx-auto px-4 py-8 space-y-8"> <div class="container mx-auto px-4 py-8 space-y-8">
<h1 class="text-3xl font-bold text-white mb-6">Dice Game</h1> <h1 class="text-3xl font-bold text-white mb-6">Dice Game</h1>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8"> <div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
<div class="lg:col-span-1 card p-8 space-y-6"> <div class="lg:col-span-1 card p-8 space-y-6">
<form [formGroup]="diceForm" (ngSubmit)="roll()" class="space-y-6"> <form [formGroup]="diceForm" (ngSubmit)="roll()" class="space-y-6">
<div class="controls space-y-4"> <div class="controls space-y-4">
<div> <div>
<label for="betAmount" class="block text-text-secondary mb-2">Bet Amount:</label> <label for="betAmount" class="block text-text-secondary mb-2">Bet Amount:</label>
<input id="betAmount" type="number" formControlName="betAmount" min="0.01" step="0.01" <input
class="w-full bg-deep-blue-light text-white rounded-lg p-2 focus:outline-none focus:ring-1 focus:ring-emerald"> id="betAmount"
@if (hasError('betAmount', 'required')) { type="number"
<span class="text-accent-red text-sm mt-1 block">Bet Amount is required</span> formControlName="betAmount"
} min="0.01"
@if (hasError('betAmount', 'min')) { step="0.01"
<span class="text-accent-red text-sm mt-1 block">Bet Amount must be at least 0.01</span> class="w-full bg-deep-blue-light text-white rounded-lg p-2 focus:outline-none focus:ring-1 focus:ring-emerald"
} />
</div> @if (hasError('betAmount', 'required')) {
<span class="text-accent-red text-sm mt-1 block">Bet Amount is required</span>
<div> }
<label class="block text-text-secondary mb-2">Roll Mode:</label> @if (hasError('betAmount', 'min')) {
<div class="roll-mode flex rounded-lg overflow-hidden"> <span class="text-accent-red text-sm mt-1 block"
<button type="button" (click)="toggleRollMode()" [ngClass]="{'bg-emerald text-white': diceForm.get('rollOver')?.value, 'bg-deep-blue-light text-text-secondary': !diceForm.get('rollOver')?.value}" >Bet Amount must be at least 0.01</span
class="flex-1 py-2 text-center font-semibold transition-colors duration-200">Roll Over</button> >
<button type="button" (click)="toggleRollMode()" [ngClass]="{'bg-emerald text-white': !diceForm.get('rollOver')?.value, 'bg-deep-blue-light text-text-secondary': diceForm.get('rollOver')?.value}" }
class="flex-1 py-2 text-center font-semibold transition-colors duration-200">Roll Under</button> </div>
</div>
</div> <div>
<label class="block text-text-secondary mb-2">Roll Mode:</label>
<div> <div class="roll-mode flex rounded-lg overflow-hidden">
<label for="targetValue" class="block text-text-secondary mb-2">Target Value: {{ diceForm.get('targetValue')?.value | number:'1.0-2' }}</label> <button
<input id="targetValue" type="range" formControlName="targetValue" min="1" max="100" step="0.01" type="button"
class="w-full h-2 bg-deep-blue-light rounded-lg appearance-none cursor-pointer range-lg accent-emerald"> (click)="toggleRollMode()"
@if (hasError('targetValue', 'required')) { [ngClass]="{
<span class="text-accent-red text-sm mt-1 block">Target Value is required</span> 'bg-emerald text-white': diceForm.get('rollOver')?.value,
} 'bg-deep-blue-light text-text-secondary': !diceForm.get('rollOver')?.value,
@if (hasError('targetValue', 'min')) { }"
<span class="text-accent-red text-sm mt-1 block">Target Value must be at least 1</span> class="flex-1 py-2 text-center font-semibold transition-colors duration-200"
} >
@if (hasError('targetValue', 'max')) { Roll Over
<span class="text-accent-red text-sm mt-1 block">Target Value must be at most 100</span> </button>
} <button
</div> type="button"
</div> (click)="toggleRollMode()"
[ngClass]="{
<div class="info space-y-2 text-text-secondary"> 'bg-emerald text-white': !diceForm.get('rollOver')?.value,
<p>Win Chance: <span class="text-white">{{ winChance() | number:'1.0-2' }}%</span></p> 'bg-deep-blue-light text-text-secondary': diceForm.get('rollOver')?.value,
<p>Potential Win: <span class="text-white">{{ potentialWin() | currency:'EUR':'symbol':'1.2-2' }}</span></p> }"
</div> class="flex-1 py-2 text-center font-semibold transition-colors duration-200"
>
<button type="submit" class="button-primary w-full py-2 font-bold">Roll Dice</button> Roll Under
</form> </button>
</div> </div>
</div>
<div class="lg:col-span-3 card p-8 flex items-center justify-center">
@if (rolledValue() !== null) { <div>
<div class="text-white text-center text-8xl font-bold"> <label for="targetValue" class="block text-text-secondary mb-2"
{{ rolledValue() }} >Target Value: {{ diceForm.get('targetValue')?.value | number: '1.0-2' }}</label
</div> >
} <input
</div> id="targetValue"
</div> type="range"
formControlName="targetValue"
min="1"
@if (rolledValue() !== null) { max="100"
<div class="result max-w-sm mx-auto card p-6 mt-8 text-center"> step="0.01"
@if (win()) { class="w-full h-2 bg-deep-blue-light rounded-lg appearance-none cursor-pointer range-lg accent-emerald"
<p class="text-emerald text-base font-semibold">You Won! Payout: {{ payout() | currency:'EUR':'symbol':'1.2-2' }}</p> />
} @else { @if (hasError('targetValue', 'required')) {
<p class="text-accent-red text-base font-semibold">You Lost.</p> <span class="text-accent-red text-sm mt-1 block">Target Value is required</span>
} }
</div> @if (hasError('targetValue', 'min')) {
} <span class="text-accent-red text-sm mt-1 block"
</div> >Target Value must be at least 1</span
>
}
@if (hasError('targetValue', 'max')) {
<span class="text-accent-red text-sm mt-1 block"
>Target Value must be at most 100</span
>
}
</div>
</div>
<div class="info space-y-2 text-text-secondary">
<p>
Win Chance: <span class="text-white">{{ winChance() | number: '1.0-2' }}%</span>
</p>
<p>
Potential Win:
<span class="text-white">{{
potentialWin() | currency: 'EUR' : 'symbol' : '1.2-2'
}}</span>
</p>
</div>
<button type="submit" class="button-primary w-full py-2 font-bold">Roll Dice</button>
</form>
</div>
<div class="lg:col-span-3 card p-8 flex items-center justify-center">
@if (rolledValue() !== null) {
<div class="text-white text-center text-8xl font-bold">
{{ rolledValue() }}
</div>
}
</div>
</div>
@if (rolledValue() !== null) {
<div class="result max-w-sm mx-auto card p-6 mt-8 text-center">
@if (win()) {
<p class="text-emerald text-base font-semibold">
You Won! Payout: {{ payout() | currency: 'EUR' : 'symbol' : '1.2-2' }}
</p>
} @else {
<p class="text-accent-red text-base font-semibold">You Lost.</p>
}
</div>
}
</div>

View file

@ -1,118 +1,124 @@
import { Component, signal, inject, OnInit } from '@angular/core'; import { Component, signal, inject, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import {
import { DiceService } from './dice.service'; FormBuilder,
import { DiceDto, DiceResult } from './dice.model'; FormControl,
import { debounceTime, tap } from 'rxjs/operators'; FormGroup,
import {UserService} from "@service/user.service"; ReactiveFormsModule,
Validators,
type DiceFormGroup = FormGroup<{ } from '@angular/forms';
betAmount: FormControl<number | null>; import { DiceService } from './dice.service';
rollOver: FormControl<boolean>; import { DiceDto, DiceResult } from './dice.model';
targetValue: FormControl<number | null>; import { debounceTime, tap } from 'rxjs/operators';
}>; import { UserService } from '@service/user.service';
@Component({ type DiceFormGroup = FormGroup<{
selector: 'app-dice', betAmount: FormControl<number | null>;
standalone: true, rollOver: FormControl<boolean>;
imports: [CommonModule, ReactiveFormsModule], targetValue: FormControl<number | null>;
templateUrl: './dice.component.html', }>;
})
export class DiceComponent implements OnInit { @Component({
private readonly formBuilder = inject(FormBuilder); selector: 'app-dice',
private readonly diceService = inject(DiceService); standalone: true,
private readonly userService = inject(UserService); imports: [CommonModule, ReactiveFormsModule],
templateUrl: './dice.component.html',
})
rolledValue = signal<number | null>(null); export class DiceComponent implements OnInit {
win = signal<boolean | null>(null); private readonly formBuilder = inject(FormBuilder);
payout = signal<number | null>(null); private readonly diceService = inject(DiceService);
winChance = signal(0); private readonly userService = inject(UserService);
potentialWin = signal(0);
rolledValue = signal<number | null>(null);
readonly diceForm: DiceFormGroup = this.createDiceForm(); win = signal<boolean | null>(null);
payout = signal<number | null>(null);
private readonly MAX_DICE_VALUE = 100; winChance = signal(0);
potentialWin = signal(0);
constructor() {
} readonly diceForm: DiceFormGroup = this.createDiceForm();
ngOnInit(): void { private readonly MAX_DICE_VALUE = 100;
this.diceForm.valueChanges.pipe(
debounceTime(100), constructor() {}
tap(() => this.calculateWinChanceAndPotentialWin())
).subscribe(); ngOnInit(): void {
this.diceForm.valueChanges
this.calculateWinChanceAndPotentialWin(); .pipe(
} debounceTime(100),
tap(() => this.calculateWinChanceAndPotentialWin())
createDiceForm(): DiceFormGroup { )
return this.formBuilder.group({ .subscribe();
betAmount: new FormControl<number | null>(1.00, {
validators: [Validators.required, Validators.min(0.01)], this.calculateWinChanceAndPotentialWin();
nonNullable: true, }
}),
rollOver: new FormControl<boolean>(true, { createDiceForm(): DiceFormGroup {
validators: [Validators.required], return this.formBuilder.group({
nonNullable: true, betAmount: new FormControl<number | null>(1.0, {
}), validators: [Validators.required, Validators.min(0.01)],
targetValue: new FormControl<number | null>(50.50, { nonNullable: true,
validators: [Validators.required, Validators.min(1), Validators.max(100)], }),
nonNullable: true, rollOver: new FormControl<boolean>(true, {
}), validators: [Validators.required],
}); nonNullable: true,
} }),
targetValue: new FormControl<number | null>(50.5, {
toggleRollMode(): void { validators: [Validators.required, Validators.min(1), Validators.max(100)],
const currentMode = this.diceForm.get('rollOver')?.value; nonNullable: true,
this.diceForm.get('rollOver')?.setValue(!currentMode); }),
} });
}
calculateWinChanceAndPotentialWin(): void {
const formValues = this.diceForm.value; toggleRollMode(): void {
const target = formValues.targetValue ?? 0; const currentMode = this.diceForm.get('rollOver')?.value;
const bet = formValues.betAmount ?? 0; this.diceForm.get('rollOver')?.setValue(!currentMode);
const isOver = formValues.rollOver ?? true; }
const calculatedWinChance = isOver ? this.MAX_DICE_VALUE - target : target - 1; calculateWinChanceAndPotentialWin(): void {
const formValues = this.diceForm.value;
this.winChance.set(Math.max(0, calculatedWinChance)); const target = formValues.targetValue ?? 0;
const bet = formValues.betAmount ?? 0;
let multiplier = 0; const isOver = formValues.rollOver ?? true;
if (calculatedWinChance > 0) {
multiplier = (this.MAX_DICE_VALUE - 1) / calculatedWinChance; const calculatedWinChance = isOver ? this.MAX_DICE_VALUE - target : target - 1;
}
this.winChance.set(Math.max(0, calculatedWinChance));
this.potentialWin.set(bet * multiplier);
} let multiplier = 0;
if (calculatedWinChance > 0) {
roll(): void { multiplier = (this.MAX_DICE_VALUE - 1) / calculatedWinChance;
if (this.diceForm.invalid) { }
this.diceForm.markAllAsTouched();
return; this.potentialWin.set(bet * multiplier);
} }
const diceDto: DiceDto = this.diceForm.getRawValue() as DiceDto; roll(): void {
if (this.diceForm.invalid) {
this.rolledValue.set(null); this.diceForm.markAllAsTouched();
this.win.set(null); return;
this.payout.set(null); }
this.diceService.rollDice(diceDto).subscribe({ const diceDto: DiceDto = this.diceForm.getRawValue() as DiceDto;
next: (result: DiceResult) => {
this.rolledValue.set(result.rolledValue); this.rolledValue.set(null);
this.win.set(result.win); this.win.set(null);
this.payout.set(result.payout); this.payout.set(null);
this.userService.refreshCurrentUser();
}, this.diceService.rollDice(diceDto).subscribe({
error: (error) => { next: (result: DiceResult) => {
console.error('Dice roll failed:', error); this.rolledValue.set(result.rolledValue);
} this.win.set(result.win);
}); this.payout.set(result.payout);
} this.userService.refreshCurrentUser();
},
hasError(controlName: string, errorName: string): boolean { error: (error) => {
const control = this.diceForm.get(controlName); console.error('Dice roll failed:', error);
return control !== null && control.touched && control.hasError(errorName); },
} });
} }
hasError(controlName: string, errorName: string): boolean {
const control = this.diceForm.get(controlName);
return control !== null && control.touched && control.hasError(errorName);
}
}

View file

@ -1,11 +1,11 @@
export interface DiceDto { export interface DiceDto {
betAmount: number; betAmount: number;
rollOver: boolean; rollOver: boolean;
targetValue: number; targetValue: number;
} }
export interface DiceResult { export interface DiceResult {
win: boolean; win: boolean;
payout: number; payout: number;
rolledValue: number; rolledValue: number;
} }

View file

@ -1,17 +1,17 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { DiceDto, DiceResult } from './dice.model'; import { DiceDto, DiceResult } from './dice.model';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class DiceService { export class DiceService {
private apiUrl = '/backend/dice'; private apiUrl = '/backend/dice';
constructor(private http: HttpClient) { } constructor(private http: HttpClient) {}
rollDice(diceDto: DiceDto): Observable<DiceResult> { rollDice(diceDto: DiceDto): Observable<DiceResult> {
return this.http.post<DiceResult>(this.apiUrl, diceDto); return this.http.post<DiceResult>(this.apiUrl, diceDto);
} }
} }