diff --git a/backend/src/main/java/de/szut/casino/lootboxes/LootBoxController.java b/backend/src/main/java/de/szut/casino/lootboxes/LootBoxController.java index 5018914..540e4c5 100644 --- a/backend/src/main/java/de/szut/casino/lootboxes/LootBoxController.java +++ b/backend/src/main/java/de/szut/casino/lootboxes/LootBoxController.java @@ -1,91 +1,91 @@ -package de.szut.casino.lootboxes; - -import de.szut.casino.exceptionHandling.exceptions.InsufficientFundsException; -import de.szut.casino.exceptionHandling.exceptions.UserNotFoundException; -import de.szut.casino.user.UserEntity; -import de.szut.casino.user.UserService; -import jakarta.validation.Valid; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -@RestController -public class LootBoxController { - private final LootBoxRepository lootBoxRepository; - private final UserService userService; - private final LootBoxService lootBoxService; - - public LootBoxController(LootBoxRepository lootBoxRepository, UserService userService, LootBoxService lootBoxService) { - this.lootBoxRepository = lootBoxRepository; - this.userService = userService; - this.lootBoxService = lootBoxService; - } - - @GetMapping("/lootboxes") - public List getAllLootBoxes() { - return lootBoxRepository.findAll(); - } - - @PostMapping("/lootboxes/{id}") - public ResponseEntity purchaseLootBox(@PathVariable Long id) { - Optional optionalLootBox = lootBoxRepository.findById(id); - if (optionalLootBox.isEmpty()) { - return ResponseEntity.notFound().build(); - } - - LootBoxEntity lootBox = optionalLootBox.get(); - - Optional optionalUser = userService.getCurrentUser(); - if (optionalUser.isEmpty()) { - throw new UserNotFoundException(); - } - - UserEntity user = optionalUser.get(); - - if (lootBoxService.hasSufficientBalance(user, lootBox.getPrice())) { - throw new InsufficientFundsException(); - } - - RewardEntity reward = lootBoxService.determineReward(lootBox); - lootBoxService.handleBalance(user, lootBox, reward); - - return ResponseEntity.ok(reward); - } - - @PostMapping("/lootboxes") - public ResponseEntity createLootbox(@RequestBody @Valid CreateLootBoxDto createLootBoxDto) { - List rewardEntities = new ArrayList<>(); - - for (CreateRewardDto createRewardDto : createLootBoxDto.getRewards()) { - rewardEntities.add(new RewardEntity(createRewardDto.getValue(), createRewardDto.getProbability())); - } - - LootBoxEntity lootBoxEntity = new LootBoxEntity( - createLootBoxDto.getName(), - createLootBoxDto.getPrice(), - rewardEntities - ); - - this.lootBoxRepository.save(lootBoxEntity); - - return ResponseEntity.ok(lootBoxEntity); - } - - @DeleteMapping("/lootboxes/{id}") - public ResponseEntity deleteLootbox(@PathVariable Long id) { - Optional optionalLootBox = lootBoxRepository.findById(id); - if (optionalLootBox.isEmpty()) { - return ResponseEntity.notFound().build(); - } - - LootBoxEntity lootBox = optionalLootBox.get(); - lootBoxRepository.delete(lootBox); - - return ResponseEntity.ok(Collections.singletonMap("message", "successfully deleted lootbox")); - } - -} +package de.szut.casino.lootboxes; + +import de.szut.casino.exceptionHandling.exceptions.InsufficientFundsException; +import de.szut.casino.exceptionHandling.exceptions.UserNotFoundException; +import de.szut.casino.user.UserEntity; +import de.szut.casino.user.UserService; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +@RestController +public class LootBoxController { + private final LootBoxRepository lootBoxRepository; + private final UserService userService; + private final LootBoxService lootBoxService; + + public LootBoxController(LootBoxRepository lootBoxRepository, UserService userService, LootBoxService lootBoxService) { + this.lootBoxRepository = lootBoxRepository; + this.userService = userService; + this.lootBoxService = lootBoxService; + } + + @GetMapping("/lootboxes") + public List getAllLootBoxes() { + return lootBoxRepository.findAll(); + } + + @PostMapping("/lootboxes/{id}") + public ResponseEntity purchaseLootBox(@PathVariable Long id) { + Optional optionalLootBox = lootBoxRepository.findById(id); + if (optionalLootBox.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + LootBoxEntity lootBox = optionalLootBox.get(); + + Optional optionalUser = userService.getCurrentUser(); + if (optionalUser.isEmpty()) { + throw new UserNotFoundException(); + } + + UserEntity user = optionalUser.get(); + + if (lootBoxService.hasSufficientBalance(user, lootBox.getPrice())) { + throw new InsufficientFundsException(); + } + + RewardEntity reward = lootBoxService.determineReward(lootBox); + lootBoxService.handleBalance(user, lootBox, reward); + + return ResponseEntity.ok(reward); + } + + @PostMapping("/lootboxes") + public ResponseEntity createLootbox(@RequestBody @Valid CreateLootBoxDto createLootBoxDto) { + List rewardEntities = new ArrayList<>(); + + for (CreateRewardDto createRewardDto : createLootBoxDto.getRewards()) { + rewardEntities.add(new RewardEntity(createRewardDto.getValue(), createRewardDto.getProbability())); + } + + LootBoxEntity lootBoxEntity = new LootBoxEntity( + createLootBoxDto.getName(), + createLootBoxDto.getPrice(), + rewardEntities + ); + + this.lootBoxRepository.save(lootBoxEntity); + + return ResponseEntity.ok(lootBoxEntity); + } + + @DeleteMapping("/lootboxes/{id}") + public ResponseEntity deleteLootbox(@PathVariable Long id) { + Optional optionalLootBox = lootBoxRepository.findById(id); + if (optionalLootBox.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + LootBoxEntity lootBox = optionalLootBox.get(); + lootBoxRepository.delete(lootBox); + + return ResponseEntity.ok(Collections.singletonMap("message", "successfully deleted lootbox")); + } + +} diff --git a/backend/src/main/java/de/szut/casino/lootboxes/LootBoxService.java b/backend/src/main/java/de/szut/casino/lootboxes/LootBoxService.java index 647b69b..d80370e 100644 --- a/backend/src/main/java/de/szut/casino/lootboxes/LootBoxService.java +++ b/backend/src/main/java/de/szut/casino/lootboxes/LootBoxService.java @@ -1,40 +1,40 @@ -package de.szut.casino.lootboxes; - -import de.szut.casino.user.UserEntity; -import de.szut.casino.user.UserRepository; -import org.springframework.stereotype.Service; - -import java.math.BigDecimal; - -@Service -public class LootBoxService { - private final UserRepository userRepository; - - public LootBoxService(UserRepository userRepository) { - this.userRepository = userRepository; - } - - public boolean hasSufficientBalance(UserEntity user, BigDecimal price) { - return user.getBalance().compareTo(price) < 0; - } - - public RewardEntity determineReward(LootBoxEntity lootBox) { - double randomValue = Math.random(); - BigDecimal cumulativeProbability = BigDecimal.ZERO; - - for (RewardEntity reward : lootBox.getRewards()) { - cumulativeProbability = cumulativeProbability.add(reward.getProbability()); - if (randomValue <= cumulativeProbability.doubleValue()) { - return reward; - } - } - - return lootBox.getRewards().getLast(); - } - - public void handleBalance(UserEntity user, LootBoxEntity lootBox, RewardEntity reward) { - user.setBalance(user.getBalance().subtract(lootBox.getPrice())); - user.setBalance(user.getBalance().add(reward.getValue())); - userRepository.save(user); - } -} +package de.szut.casino.lootboxes; + +import de.szut.casino.user.UserEntity; +import de.szut.casino.user.UserRepository; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; + +@Service +public class LootBoxService { + private final UserRepository userRepository; + + public LootBoxService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public boolean hasSufficientBalance(UserEntity user, BigDecimal price) { + return user.getBalance().compareTo(price) < 0; + } + + public RewardEntity determineReward(LootBoxEntity lootBox) { + double randomValue = Math.random(); + BigDecimal cumulativeProbability = BigDecimal.ZERO; + + for (RewardEntity reward : lootBox.getRewards()) { + cumulativeProbability = cumulativeProbability.add(reward.getProbability()); + if (randomValue <= cumulativeProbability.doubleValue()) { + return reward; + } + } + + return lootBox.getRewards().getLast(); + } + + public void handleBalance(UserEntity user, LootBoxEntity lootBox, RewardEntity reward) { + user.setBalance(user.getBalance().subtract(lootBox.getPrice())); + user.setBalance(user.getBalance().add(reward.getValue())); + userRepository.save(user); + } +} diff --git a/backend/src/main/java/de/szut/casino/slots/SlotService.java b/backend/src/main/java/de/szut/casino/slots/SlotService.java index 7905636..4bdaa90 100644 --- a/backend/src/main/java/de/szut/casino/slots/SlotService.java +++ b/backend/src/main/java/de/szut/casino/slots/SlotService.java @@ -54,13 +54,12 @@ public class SlotService { SpinResult spinResult = new SpinResult(); spinResult.setStatus(status.name().toLowerCase()); - this.balanceService.subtractFunds(user, betAmount); - if (status == Status.WIN) { BigDecimal winAmount = betAmount.multiply(winSymbol.getPayoutMultiplier()); this.balanceService.addFunds(user, winAmount); spinResult.setAmount(winAmount); } else { + this.balanceService.subtractFunds(user, betAmount); spinResult.setAmount(betAmount); } diff --git a/frontend/src/app/feature/game/blackjack/blackjack.component.ts b/frontend/src/app/feature/game/blackjack/blackjack.component.ts index c21a012..2763b55 100644 --- a/frontend/src/app/feature/game/blackjack/blackjack.component.ts +++ b/frontend/src/app/feature/game/blackjack/blackjack.component.ts @@ -14,7 +14,6 @@ import { NavbarComponent } from '@shared/components/navbar/navbar.component'; import { UserService } from '@service/user.service'; import { timer } from 'rxjs'; import { DebtDialogComponent } from '@shared/components/debt-dialog/debt-dialog.component'; -import { AuthService } from '@service/auth.service'; @Component({ selector: 'app-blackjack', @@ -35,7 +34,6 @@ import { AuthService } from '@service/auth.service'; export default class BlackjackComponent implements OnInit { private router = inject(Router); private userService = inject(UserService); - private authService = inject(AuthService); private blackjackService = inject(BlackjackService); dealerCards = signal([]); @@ -53,8 +51,7 @@ export default class BlackjackComponent implements OnInit { debtAmount = signal(0); ngOnInit(): void { - // Subscribe to user updates for real-time balance changes - this.authService.userSubject.subscribe((user) => { + this.userService.getCurrentUser().subscribe((user) => { if (user) { this.balance.set(user.balance); } @@ -87,14 +84,9 @@ export default class BlackjackComponent implements OnInit { if (isGameOver) { console.log('Game is over, state:', game.state); this.userService.refreshCurrentUser(); - - // Get the latest balance before showing the result dialog - timer(1000).subscribe(() => { - // Show the result dialog after refreshing user data - timer(500).subscribe(() => { - this.showGameResult.set(true); - console.log('Game result dialog shown after delay'); - }); + timer(1500).subscribe(() => { + this.showGameResult.set(true); + console.log('Game result dialog shown after delay'); }); } } @@ -173,16 +165,12 @@ export default class BlackjackComponent implements OnInit { this.blackjackService.doubleDown(this.currentGameId()!).subscribe({ next: (game) => { this.updateGameState(game); - - // Wait a bit to ensure the backend has finished processing - timer(1000).subscribe(() => { - const user = this.authService.currentUserValue; + this.userService.getCurrentUser().subscribe((user) => { if (user && user.balance < 0) { this.debtAmount.set(Math.abs(user.balance)); this.showDebtDialog.set(true); } }); - this.isActionInProgress.set(false); }, error: (error) => { @@ -196,6 +184,7 @@ export default class BlackjackComponent implements OnInit { onCloseGameResult(): void { console.log('Closing game result dialog'); this.showGameResult.set(false); + this.userService.refreshCurrentUser(); } onCloseDebtDialog(): void { diff --git a/frontend/src/app/feature/game/slots/slots.component.css b/frontend/src/app/feature/game/slots/slots.component.css deleted file mode 100644 index c1fa452..0000000 --- a/frontend/src/app/feature/game/slots/slots.component.css +++ /dev/null @@ -1,16 +0,0 @@ -/* Symbol colors */ -.symbol-BAR { - color: var(--color-accent-yellow); -} -.symbol-SEVEN { - color: var(--color-accent-red); -} -.symbol-BELL { - color: var(--color-accent-purple); -} -.symbol-CHERRY { - color: #ec4899; -} -.symbol-LEMON { - color: #a3e635; -} diff --git a/frontend/src/app/feature/game/slots/slots.component.html b/frontend/src/app/feature/game/slots/slots.component.html index c5929df..b6d9318 100644 --- a/frontend/src/app/feature/game/slots/slots.component.html +++ b/frontend/src/app/feature/game/slots/slots.component.html @@ -1,177 +1,50 @@ -
-

Spielautomaten

+
+

Payouts

+ @if (slotInfo(); as info) { + + + @for (item of info | keyvalue; track item.key) { + + + + + } + +
{{ item.key }}{{ item.value }}
+ } -
- -
-
- -
-
-

Slot Machine

-
- - {{ - slotResult().status === 'win' - ? 'Gewonnen!' - : slotResult().status === 'lose' - ? 'Verloren' - : 'Bereit' - }} - -
-
-
- - -
-
-
- @for (row of slotResult().resultMatrix; track $index) { - @for (cell of row; track $index) { -
- {{ - cell - }} -
- } - } -
-
- - -
-
- +{{ slotResult().amount | currency: 'EUR' }} -
-
- - -
-
- - -
- - -
-
-
+
+
+ @for (row of slotResult().resultMatrix; track $index) { + @for (cell of row; track $index) { +
{{ cell }}
+ } + }
- -
-
-

Spiel Informationen

-
-
- Kontostand: - - - -
-
- Einsatz: - - - -
- -
- - - - -
- -

Auszahlungen:

- - @if (slotInfo(); as info) { -
    - @for (item of info | keyvalue; track item.key) { -
  • -
    - {{ item.key }} -
    - {{ item.value }}x -
  • - } -
- } @else { -
-
-
- } - -
-

Spielregeln:

-
    -
  • • Gewinne mit 3 gleichen Symbolen
  • -
  • • Höhere Symbole = höhere Gewinne
  • -
-
-
-
+
+

+ Game result: {{ slotResult().status | uppercase }} +

+

+ Amount: {{ slotResult().amount }} +

+ +
+ + +
+ +
diff --git a/frontend/src/app/feature/game/slots/slots.component.ts b/frontend/src/app/feature/game/slots/slots.component.ts index 71fa482..ad73a03 100644 --- a/frontend/src/app/feature/game/slots/slots.component.ts +++ b/frontend/src/app/feature/game/slots/slots.component.ts @@ -1,19 +1,8 @@ -import { - ChangeDetectionStrategy, - Component, - inject, - OnInit, - OnDestroy, - signal, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core'; import { NavbarComponent } from '@shared/components/navbar/navbar.component'; import { HttpClient } from '@angular/common/http'; -import { CommonModule, KeyValuePipe, NgClass, CurrencyPipe } from '@angular/common'; +import { KeyValuePipe, UpperCasePipe } from '@angular/common'; import { FormsModule } from '@angular/forms'; -import { UserService } from '@service/user.service'; -import { Subscription } from 'rxjs'; -import { AnimatedNumberComponent } from '@blackjack/components/animated-number/animated-number.component'; -import { AuthService } from '@service/auth.service'; interface SlotResult { status: 'win' | 'lose' | 'blank' | 'start'; @@ -24,25 +13,12 @@ interface SlotResult { @Component({ selector: 'app-slots', standalone: true, - imports: [ - CommonModule, - NavbarComponent, - KeyValuePipe, - NgClass, - FormsModule, - CurrencyPipe, - AnimatedNumberComponent, - ], + imports: [NavbarComponent, KeyValuePipe, UpperCasePipe, FormsModule], templateUrl: './slots.component.html', - styleUrl: './slots.component.css', changeDetection: ChangeDetectionStrategy.OnPush, }) -export default class SlotsComponent implements OnInit, OnDestroy { +export default class SlotsComponent implements OnInit { private httpClient: HttpClient = inject(HttpClient); - private userService = inject(UserService); - private authService = inject(AuthService); - private userSubscription: Subscription | undefined; - slotInfo = signal | null>(null); slotResult = signal({ status: 'start', @@ -53,80 +29,21 @@ export default class SlotsComponent implements OnInit, OnDestroy { ['BELL', 'BELL', 'BELL'], ], }); - - balance = signal(0); betAmount = signal(1); - isSpinning = false; ngOnInit(): void { this.httpClient.get>('/backend/slots/info').subscribe((data) => { this.slotInfo.set(data); }); - - this.userSubscription = this.authService.userSubject.subscribe((user) => { - this.balance.set(user?.balance ?? 0); - }); - - this.userService.refreshCurrentUser(); - } - - ngOnDestroy(): void { - if (this.userSubscription) { - this.userSubscription.unsubscribe(); - } - } - - getSymbolClass(symbol: string): string { - return `symbol-${symbol}`; - } - - hasEnoughBalance(): boolean { - return this.balance() >= this.betAmount(); - } - - setBetAmount(percentage: number): void { - const calculatedBet = Math.floor(this.balance() * percentage * 100) / 100; - const minimumBet = 0.01; - - const newBet = Math.max(minimumBet, Math.min(calculatedBet, this.balance())); - - this.betAmount.set(newBet); } spin(): void { - if (!this.hasEnoughBalance()) { - return; - } - - this.isSpinning = true; - const betAmount = this.betAmount(); - - this.userService.updateLocalBalance(-betAmount); - const payload = { - betAmount: betAmount, + betAmount: this.betAmount(), }; - this.httpClient.post('/backend/slots/spin', payload).subscribe({ - next: (result) => { - setTimeout(() => { - this.slotResult.set(result); - - if (result.status === 'win') { - this.userService.updateLocalBalance(result.amount); - } - - this.userService.refreshCurrentUser(); - - this.isSpinning = false; - }, 1500); - }, - error: (err) => { - console.error('Error spinning slot machine:', err); - this.userService.updateLocalBalance(betAmount); - this.userService.refreshCurrentUser(); - this.isSpinning = false; - }, + this.httpClient.post('/backend/slots/spin', payload).subscribe((result) => { + this.slotResult.set(result); }); } } diff --git a/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.ts b/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.ts index 97e5200..3d84d9d 100644 --- a/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.ts +++ b/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.ts @@ -6,7 +6,6 @@ import { LootBox, Reward } from 'app/model/LootBox'; import { NavbarComponent } from '@shared/components/navbar/navbar.component'; import { UserService } from '@service/user.service'; import { User } from 'app/model/User'; -import { AuthService } from '@service/auth.service'; @Component({ selector: 'app-lootbox-opening', @@ -31,11 +30,10 @@ export default class LootboxOpeningComponent { private router: Router, private lootboxService: LootboxService, private userService: UserService, - private authService: AuthService, private cdr: ChangeDetectorRef ) { this.loadLootbox(); - this.authService.userSubject.subscribe((user) => { + this.userService.currentUser$.subscribe((user) => { this.currentUser = user; this.cdr.detectChanges(); }); diff --git a/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.ts b/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.ts index dc39869..747e29e 100644 --- a/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.ts +++ b/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.ts @@ -5,9 +5,8 @@ import { LootboxService } from '../services/lootbox.service'; import { LootBox } from 'app/model/LootBox'; import { Router } from '@angular/router'; import { timeout } from 'rxjs'; -import { User } from 'app/model/User'; -import { AuthService } from '@service/auth.service'; import { UserService } from '@service/user.service'; +import { User } from 'app/model/User'; @Component({ selector: 'app-lootbox-selection', @@ -91,13 +90,12 @@ export default class LootboxSelectionComponent implements OnInit { private lootboxService: LootboxService, private router: Router, private cdr: ChangeDetectorRef, - private authService: AuthService, private userService: UserService ) {} ngOnInit(): void { this.loadLootboxes(); - this.authService.userSubject.subscribe((user) => { + this.userService.currentUser$.subscribe((user) => { this.currentUser = user; this.cdr.detectChanges(); }); diff --git a/frontend/src/app/service/user.service.ts b/frontend/src/app/service/user.service.ts index e126fc4..d7199b3 100644 --- a/frontend/src/app/service/user.service.ts +++ b/frontend/src/app/service/user.service.ts @@ -1,33 +1,38 @@ import { inject, Injectable } from '@angular/core'; -import { AuthService } from '@service/auth.service'; +import { HttpClient } from '@angular/common/http'; +import { BehaviorSubject, catchError, EMPTY, Observable, tap } from 'rxjs'; import { User } from '../model/User'; +import { AuthService } from '@service/auth.service'; @Injectable({ providedIn: 'root', }) export class UserService { + public currentUserSubject = new BehaviorSubject(null); + public currentUser$ = this.currentUserSubject.asObservable(); + private http: HttpClient = inject(HttpClient); private authService = inject(AuthService); - /** - * Updates the user's balance locally for immediate UI feedback - * This should be called before a server-side balance change is made - * The server update will be reflected when AuthService.loadCurrentUser() is called - */ + public getCurrentUser(): Observable { + return this.http.get('/backend/users/me').pipe( + catchError(() => EMPTY), + tap((user) => this.currentUserSubject.next(user)) + ); + } + + public refreshCurrentUser(): void { + this.getCurrentUser().subscribe(); + this.authService.loadCurrentUser(); + } + public updateLocalBalance(amount: number): void { - const currentUser = this.authService.currentUserValue; + const currentUser = this.currentUserSubject.getValue(); if (currentUser) { - const updatedUser: User = { + const updatedUser = { ...currentUser, balance: currentUser.balance + amount, }; - this.authService.userSubject.next(updatedUser); + this.currentUserSubject.next(updatedUser); } } - - /** - * Refreshes the current user's data from the server - */ - public refreshCurrentUser(): void { - this.authService.loadCurrentUser(); - } }