diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 39c5b1c..b8adc5f 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..5cc7a0e --- /dev/null +++ b/frontend/src/app/feature/game/dice/dice.component.html @@ -0,0 +1,121 @@ +
+

Dice Game

+ +
+
+
+
+
+ + + @if (hasError('betAmount', 'required')) { + Bet Amount is required + } + @if (hasError('betAmount', 'min')) { + Bet Amount must be at least 0.01 + } +
+ +
+
Roll Mode:
+
+ + +
+
+ +
+ + + @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) { +
+ {{ rolledValue() }} +
+ } +
+
+ + @if (rolledValue() !== null) { +
+ @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..61cc1e4 --- /dev/null +++ b/frontend/src/app/feature/game/dice/dice.component.ts @@ -0,0 +1,122 @@ +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; + + ngOnInit(): void { + this.diceForm.valueChanges + .pipe( + debounceTime(100), + tap(() => this.calculateWinChanceAndPotentialWin()) + ) + .subscribe(); + + this.calculateWinChanceAndPotentialWin(); + } + + createDiceForm(): DiceFormGroup { + return this.formBuilder.group({ + betAmount: new FormControl(1.0, { + validators: [Validators.required, Validators.min(0.01)], + nonNullable: true, + }), + rollOver: new FormControl(true, { + validators: [Validators.required], + nonNullable: true, + }), + targetValue: new FormControl(50.5, { + 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..93a1133 --- /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..a071495 --- /dev/null +++ b/frontend/src/app/feature/game/dice/dice.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { DiceDto, DiceResult } from './dice.model'; +import { environment } from '@environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class DiceService { + private apiUrl = `${environment.apiUrl}/dice`; + + constructor(private http: HttpClient) {} + + rollDice(diceDto: DiceDto): Observable { + return this.http.post(this.apiUrl, diceDto); + } +} diff --git a/frontend/src/app/feature/home/home.component.ts b/frontend/src/app/feature/home/home.component.ts index 07b5f9f..494c03f 100644 --- a/frontend/src/app/feature/home/home.component.ts +++ b/frontend/src/app/feature/home/home.component.ts @@ -72,9 +72,9 @@ export default class HomeComponent implements OnInit { }, { id: '5', - name: 'Liars Dice', + name: 'Dice', image: '/liars-dice.webp', - route: '/game/liars-dice', + route: '/game/dice', }, { id: '6', diff --git a/frontend/src/app/feature/landing/landing.component.html b/frontend/src/app/feature/landing/landing.component.html index b7c6a4b..4cc9c52 100644 --- a/frontend/src/app/feature/landing/landing.component.html +++ b/frontend/src/app/feature/landing/landing.component.html @@ -91,10 +91,10 @@
-

Liars Dice

-

Würfelspiel mit Strategie

+

Dice

+

Würfelspiel

Jetzt Spielen