Compare commits
4 commits
Author | SHA1 | Date | |
---|---|---|---|
84feb5f080 |
|||
566ea569e1 |
|||
6d353cc202 |
|||
5809757bc9 |
12 changed files with 140 additions and 1 deletions
BIN
frontend/public/sounds/bet.mp3
Normal file
BIN
frontend/public/sounds/bet.mp3
Normal file
Binary file not shown.
BIN
frontend/public/sounds/coinflip.mp3
Normal file
BIN
frontend/public/sounds/coinflip.mp3
Normal file
Binary file not shown.
BIN
frontend/public/sounds/drag.mp3
Normal file
BIN
frontend/public/sounds/drag.mp3
Normal file
Binary file not shown.
BIN
frontend/public/sounds/win.mp3
Normal file
BIN
frontend/public/sounds/win.mp3
Normal file
Binary file not shown.
|
@ -1,10 +1,12 @@
|
||||||
import { Component, HostListener, signal } from '@angular/core';
|
import { Component, HostListener, inject, signal } from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
import { NavbarComponent } from './shared/components/navbar/navbar.component';
|
import { NavbarComponent } from './shared/components/navbar/navbar.component';
|
||||||
import { FooterComponent } from './shared/components/footer/footer.component';
|
import { FooterComponent } from './shared/components/footer/footer.component';
|
||||||
import { LoginComponent } from './feature/auth/login/login.component';
|
import { LoginComponent } from './feature/auth/login/login.component';
|
||||||
import { RegisterComponent } from './feature/auth/register/register.component';
|
import { RegisterComponent } from './feature/auth/register/register.component';
|
||||||
import { RecoverPasswordComponent } from './feature/auth/recover-password/recover-password.component';
|
import { RecoverPasswordComponent } from './feature/auth/recover-password/recover-password.component';
|
||||||
|
import { PlaySoundDirective } from './shared/directives/play-sound.directive';
|
||||||
|
import { SoundInitializerService } from './shared/services/sound-initializer.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
|
@ -16,14 +18,22 @@ import { RecoverPasswordComponent } from './feature/auth/recover-password/recove
|
||||||
LoginComponent,
|
LoginComponent,
|
||||||
RegisterComponent,
|
RegisterComponent,
|
||||||
RecoverPasswordComponent,
|
RecoverPasswordComponent,
|
||||||
|
PlaySoundDirective,
|
||||||
],
|
],
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
|
hostDirectives: [PlaySoundDirective],
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
|
private soundInitializer = inject(SoundInitializerService);
|
||||||
|
|
||||||
showLogin = signal(false);
|
showLogin = signal(false);
|
||||||
showRegister = signal(false);
|
showRegister = signal(false);
|
||||||
showRecoverPassword = signal(false);
|
showRecoverPassword = signal(false);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.soundInitializer.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
@HostListener('document:keydown.escape')
|
@HostListener('document:keydown.escape')
|
||||||
handleEscapeKey() {
|
handleEscapeKey() {
|
||||||
this.hideAuthForms();
|
this.hideAuthForms();
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { UserService } from '@service/user.service';
|
||||||
import { timer } from 'rxjs';
|
import { timer } from 'rxjs';
|
||||||
import { DebtDialogComponent } from '@shared/components/debt-dialog/debt-dialog.component';
|
import { DebtDialogComponent } from '@shared/components/debt-dialog/debt-dialog.component';
|
||||||
import { AuthService } from '@service/auth.service';
|
import { AuthService } from '@service/auth.service';
|
||||||
|
import { AudioService } from '@shared/services/audio.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-blackjack',
|
selector: 'app-blackjack',
|
||||||
|
@ -35,6 +36,7 @@ export default class BlackjackComponent implements OnInit {
|
||||||
private userService = inject(UserService);
|
private userService = inject(UserService);
|
||||||
private authService = inject(AuthService);
|
private authService = inject(AuthService);
|
||||||
private blackjackService = inject(BlackjackService);
|
private blackjackService = inject(BlackjackService);
|
||||||
|
private audioService = inject(AudioService);
|
||||||
|
|
||||||
dealerCards = signal<Card[]>([]);
|
dealerCards = signal<Card[]>([]);
|
||||||
playerCards = signal<Card[]>([]);
|
playerCards = signal<Card[]>([]);
|
||||||
|
@ -91,6 +93,9 @@ export default class BlackjackComponent implements OnInit {
|
||||||
// Show the result dialog after refreshing user data
|
// Show the result dialog after refreshing user data
|
||||||
timer(500).subscribe(() => {
|
timer(500).subscribe(() => {
|
||||||
this.showGameResult.set(true);
|
this.showGameResult.set(true);
|
||||||
|
if (game.state === GameState.PLAYER_WON || game.state === GameState.PLAYER_BLACKJACK) {
|
||||||
|
this.audioService.playWinSound();
|
||||||
|
}
|
||||||
console.log('Game result dialog shown after delay');
|
console.log('Game result dialog shown after delay');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -99,6 +104,7 @@ export default class BlackjackComponent implements OnInit {
|
||||||
|
|
||||||
onNewGame(bet: number): void {
|
onNewGame(bet: number): void {
|
||||||
this.isActionInProgress.set(true);
|
this.isActionInProgress.set(true);
|
||||||
|
this.audioService.playBetSound();
|
||||||
|
|
||||||
this.blackjackService.startGame(bet).subscribe({
|
this.blackjackService.startGame(bet).subscribe({
|
||||||
next: (game) => {
|
next: (game) => {
|
||||||
|
@ -117,6 +123,7 @@ export default class BlackjackComponent implements OnInit {
|
||||||
if (!this.currentGameId() || this.isActionInProgress()) return;
|
if (!this.currentGameId() || this.isActionInProgress()) return;
|
||||||
|
|
||||||
this.isActionInProgress.set(true);
|
this.isActionInProgress.set(true);
|
||||||
|
this.audioService.playBetSound();
|
||||||
|
|
||||||
this.blackjackService.hit(this.currentGameId()!).subscribe({
|
this.blackjackService.hit(this.currentGameId()!).subscribe({
|
||||||
next: (game) => {
|
next: (game) => {
|
||||||
|
@ -143,6 +150,7 @@ export default class BlackjackComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isActionInProgress.set(true);
|
this.isActionInProgress.set(true);
|
||||||
|
this.audioService.playBetSound();
|
||||||
|
|
||||||
this.blackjackService.stand(this.currentGameId()!).subscribe({
|
this.blackjackService.stand(this.currentGameId()!).subscribe({
|
||||||
next: (game) => {
|
next: (game) => {
|
||||||
|
@ -167,6 +175,7 @@ export default class BlackjackComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isActionInProgress.set(true);
|
this.isActionInProgress.set(true);
|
||||||
|
this.audioService.playBetSound();
|
||||||
|
|
||||||
this.blackjackService.doubleDown(this.currentGameId()!).subscribe({
|
this.blackjackService.doubleDown(this.currentGameId()!).subscribe({
|
||||||
next: (game) => {
|
next: (game) => {
|
||||||
|
|
|
@ -40,6 +40,7 @@ export default class SlotsComponent implements OnInit, OnDestroy {
|
||||||
private userService = inject(UserService);
|
private userService = inject(UserService);
|
||||||
private authService = inject(AuthService);
|
private authService = inject(AuthService);
|
||||||
private userSubscription: Subscription | undefined;
|
private userSubscription: Subscription | undefined;
|
||||||
|
private winSound: HTMLAudioElement;
|
||||||
|
|
||||||
slotInfo = signal<Record<string, number> | null>(null);
|
slotInfo = signal<Record<string, number> | null>(null);
|
||||||
slotResult = signal<SlotResult>({
|
slotResult = signal<SlotResult>({
|
||||||
|
@ -56,6 +57,10 @@ export default class SlotsComponent implements OnInit, OnDestroy {
|
||||||
betAmount = signal<number>(1);
|
betAmount = signal<number>(1);
|
||||||
isSpinning = false;
|
isSpinning = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.winSound = new Audio('/sounds/win.mp3');
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.httpClient.get<Record<string, number>>('/backend/slots/info').subscribe((data) => {
|
this.httpClient.get<Record<string, number>>('/backend/slots/info').subscribe((data) => {
|
||||||
this.slotInfo.set(data);
|
this.slotInfo.set(data);
|
||||||
|
@ -111,6 +116,7 @@ export default class SlotsComponent implements OnInit, OnDestroy {
|
||||||
this.slotResult.set(result);
|
this.slotResult.set(result);
|
||||||
|
|
||||||
if (result.status === 'win') {
|
if (result.status === 'win') {
|
||||||
|
this.winSound.play();
|
||||||
this.userService.updateLocalBalance(result.amount);
|
this.userService.updateLocalBalance(result.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default class LootboxOpeningComponent {
|
||||||
prizeList: Reward[] = [];
|
prizeList: Reward[] = [];
|
||||||
animationCompleted = false;
|
animationCompleted = false;
|
||||||
currentUser: User | null = null;
|
currentUser: User | null = null;
|
||||||
|
private winSound: HTMLAudioElement;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -33,6 +34,7 @@ export default class LootboxOpeningComponent {
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private cdr: ChangeDetectorRef
|
private cdr: ChangeDetectorRef
|
||||||
) {
|
) {
|
||||||
|
this.winSound = new Audio('/sounds/win.mp3');
|
||||||
this.loadLootbox();
|
this.loadLootbox();
|
||||||
this.authService.userSubject.subscribe((user) => {
|
this.authService.userSubject.subscribe((user) => {
|
||||||
this.currentUser = user;
|
this.currentUser = user;
|
||||||
|
@ -145,6 +147,7 @@ export default class LootboxOpeningComponent {
|
||||||
this.animationCompleted = true;
|
this.animationCompleted = true;
|
||||||
|
|
||||||
if (this.wonReward) {
|
if (this.wonReward) {
|
||||||
|
this.winSound.play();
|
||||||
this.userService.updateLocalBalance(this.wonReward.value);
|
this.userService.updateLocalBalance(this.wonReward.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
15
frontend/src/app/shared/directives/play-sound.directive.ts
Normal file
15
frontend/src/app/shared/directives/play-sound.directive.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { Directive, HostListener, inject } from '@angular/core';
|
||||||
|
import { AudioService } from '../services/audio.service';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[appPlaySound]',
|
||||||
|
standalone: true,
|
||||||
|
})
|
||||||
|
export class PlaySoundDirective {
|
||||||
|
private audioService = inject(AudioService);
|
||||||
|
|
||||||
|
@HostListener('click')
|
||||||
|
onClick() {
|
||||||
|
this.audioService.playBetSound();
|
||||||
|
}
|
||||||
|
}
|
30
frontend/src/app/shared/services/audio.service.ts
Normal file
30
frontend/src/app/shared/services/audio.service.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class AudioService {
|
||||||
|
private audioCache = new Map<string, HTMLAudioElement>();
|
||||||
|
|
||||||
|
private getAudio(soundName: string): HTMLAudioElement {
|
||||||
|
if (this.audioCache.has(soundName)) {
|
||||||
|
return this.audioCache.get(soundName)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
const audio = new Audio(`/sounds/${soundName}.mp3`);
|
||||||
|
this.audioCache.set(soundName, audio);
|
||||||
|
return audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
playBetSound(): void {
|
||||||
|
const audio = this.getAudio('bet.mp3');
|
||||||
|
audio.currentTime = 0;
|
||||||
|
audio.play().catch((error) => console.error('Error playing bet sound:', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
playWinSound(): void {
|
||||||
|
const audio = this.getAudio('win.mp3');
|
||||||
|
audio.currentTime = 0;
|
||||||
|
audio.play().catch((error) => console.error('Error playing win sound:', error));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { Injectable, Renderer2, RendererFactory2 } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class SoundInitializerService {
|
||||||
|
private renderer: Renderer2;
|
||||||
|
private observer: MutationObserver;
|
||||||
|
|
||||||
|
constructor(rendererFactory: RendererFactory2) {
|
||||||
|
this.renderer = rendererFactory.createRenderer(null, null);
|
||||||
|
|
||||||
|
this.observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
mutation.addedNodes.forEach((node) => {
|
||||||
|
if (node instanceof HTMLElement) {
|
||||||
|
this.processElement(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
document.querySelectorAll('button, a').forEach((element) => {
|
||||||
|
if (!element.hasAttribute('appPlaySound')) {
|
||||||
|
this.renderer.setAttribute(element, 'appPlaySound', '');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private processElement(element: HTMLElement) {
|
||||||
|
if (
|
||||||
|
(element.tagName === 'BUTTON' || element.tagName === 'A') &&
|
||||||
|
!element.hasAttribute('appPlaySound')
|
||||||
|
) {
|
||||||
|
this.renderer.setAttribute(element, 'appPlaySound', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
element.querySelectorAll('button, a').forEach((child) => {
|
||||||
|
if (!child.hasAttribute('appPlaySound')) {
|
||||||
|
this.renderer.setAttribute(child, 'appPlaySound', '');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -174,3 +174,18 @@ a {
|
||||||
.modal-card .button-secondary {
|
.modal-card .button-secondary {
|
||||||
@apply bg-deep-blue-light/50 hover:bg-deep-blue-light w-full py-2.5 my-2 border border-deep-blue-light/30 hover:border-deep-blue-light/50;
|
@apply bg-deep-blue-light/50 hover:bg-deep-blue-light w-full py-2.5 my-2 border border-deep-blue-light/30 hover:border-deep-blue-light/50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
a {
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[appPlaySound],
|
||||||
|
a[appPlaySound] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:not([appPlaySound]),
|
||||||
|
a:not([appPlaySound]) {
|
||||||
|
--add-sound-directive: true;
|
||||||
|
}
|
||||||
|
|
Reference in a new issue