From a62d2092b3a1e5f47a7a7cf229349ced6e0ed2dc Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 21 May 2025 10:23:03 +0200 Subject: [PATCH] feat: implement basic dice frontend funcionality --- frontend/src/app/app.routes.ts | 5 + .../app/feature/game/dice/dice.component.html | 52 ++++++++ .../app/feature/game/dice/dice.component.ts | 118 ++++++++++++++++++ .../src/app/feature/game/dice/dice.model.ts | 11 ++ .../src/app/feature/game/dice/dice.service.ts | 17 +++ 5 files changed, 203 insertions(+) create mode 100644 frontend/src/app/feature/game/dice/dice.component.html create mode 100644 frontend/src/app/feature/game/dice/dice.component.ts create mode 100644 frontend/src/app/feature/game/dice/dice.model.ts create mode 100644 frontend/src/app/feature/game/dice/dice.service.ts diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 39c5b1c..397d7c9 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -61,4 +61,9 @@ export const routes: Routes = [ loadComponent: () => import('./feature/lootboxes/lootbox-opening/lootbox-opening.component'), canActivate: [authGuard], }, + { + path: 'game/dice', + loadComponent: () => import('./feature/game/dice/dice.component').then(m => m.DiceComponent), + canActivate: [authGuard], + }, ]; diff --git a/frontend/src/app/feature/game/dice/dice.component.html b/frontend/src/app/feature/game/dice/dice.component.html new file mode 100644 index 0000000..4278fb1 --- /dev/null +++ b/frontend/src/app/feature/game/dice/dice.component.html @@ -0,0 +1,52 @@ +
+

Dice Game

+ +
+
+ + + @if (hasError('betAmount', 'required')) { + Bet Amount is required + } + @if (hasError('betAmount', 'min')) { + Bet Amount must be at least 0.01 + } + +
+ + +
+ + + + @if (hasError('targetValue', 'required')) { + Target Value is required + } + @if (hasError('targetValue', 'min')) { + Target Value must be at least 1 + } + @if (hasError('targetValue', 'max')) { + Target Value must be at most 100 + } +
+ +
+

Win Chance: {{ winChance() | number:'1.0-2' }}%

+

Potential Win: {{ potentialWin() | currency:'EUR':'symbol':'1.2-2' }}

+
+ + +
+ + @if (rolledValue() !== null) { +
+

Result

+

Rolled Value: {{ rolledValue() }}

+ @if (win()) { +

You Won! Payout: {{ payout() | currency:'EUR':'symbol':'1.2-2' }}

+ } @else { +

You Lost.

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