import { Injectable, DestroyRef, inject } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { delay, Subscription } from 'rxjs'; import { GameApiService } from './game-api.service'; import { getErrorMessage } from '../core/http/error-message.util'; import { GameFull, GameState } from '../models/game.models'; @Injectable({ providedIn: 'root' }) export class BotMoveService { private readonly gameApi = inject(GameApiService); private readonly destroyRef = inject(DestroyRef); private botMoveSubscription: Subscription | null = null; private isBotPlayer(playerId: string): boolean { return playerId.startsWith('bot-'); } isPlayingAgainstBot(game: GameFull | null): boolean { if (!game) { return false; } return this.isBotPlayer(game.white.id) || this.isBotPlayer(game.black.id); } isCurrentPlayerBot(game: GameFull | null, state: GameState | null): boolean { if (!game || !state) { return false; } const currentPlayer = state.turn === 'white' ? game.white : game.black; return this.isBotPlayer(currentPlayer.id); } tryMakeBotMove( gameId: string, game: GameFull | null, state: GameState | null, onSuccess: (updatedState: GameState) => void, onError: (error: string) => void ): void { if (!this.isPlayingAgainstBot(game) || !this.isCurrentPlayerBot(game, state) || !state) { return; } this.botMoveSubscription?.unsubscribe(); this.botMoveSubscription = this.gameApi .getLegalMoves(gameId) .pipe( delay(1000), takeUntilDestroyed(this.destroyRef) ) .subscribe({ next: (response) => { if (response.moves.length === 0) { return; } const botMove = response.moves[Math.floor(Math.random() * response.moves.length)]; this.gameApi .makeMove(gameId, botMove.uci) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: (updatedState) => { onSuccess(updatedState); }, error: (error) => { onError(getErrorMessage(error, 'Bot move failed.')); } }); }, error: () => { onError('Could not get legal moves for bot move.'); } }); } cleanup(): void { this.botMoveSubscription?.unsubscribe(); } }