feat: 1vs BOT
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { DestroyRef, Injectable, OnDestroy, inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { interval, startWith, Subscription, switchMap } from 'rxjs';
|
||||
import { interval, startWith, Subscription, switchMap, delay } from 'rxjs';
|
||||
import { getPieceAtSquare, isPieceColor } from '../../core/chess/fen.utils';
|
||||
import { getErrorMessage } from '../../core/http/error-message.util';
|
||||
import { GameFull, GameState, GameStreamEvent, LegalMove } from '../../models/game.models';
|
||||
@@ -25,16 +25,37 @@ export class GameFacade implements OnDestroy {
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private streamSubscription: Subscription | null = null;
|
||||
private pollSubscription: Subscription | null = null;
|
||||
private botMoveSubscription: Subscription | null = null;
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.streamSubscription?.unsubscribe();
|
||||
this.pollSubscription?.unsubscribe();
|
||||
this.botMoveSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
get state(): GameState | null {
|
||||
return this.game?.state ?? null;
|
||||
}
|
||||
|
||||
private isBotPlayer(playerId: string): boolean {
|
||||
return playerId.startsWith('bot-');
|
||||
}
|
||||
|
||||
private isPlayingAgainstBot(): boolean {
|
||||
if (!this.game) {
|
||||
return false;
|
||||
}
|
||||
return this.isBotPlayer(this.game.white.id) || this.isBotPlayer(this.game.black.id);
|
||||
}
|
||||
|
||||
private isCurrentPlayerBot(): boolean {
|
||||
if (!this.game || !this.state) {
|
||||
return false;
|
||||
}
|
||||
const currentPlayer = this.state.turn === 'white' ? this.game.white : this.game.black;
|
||||
return this.isBotPlayer(currentPlayer.id);
|
||||
}
|
||||
|
||||
setGameId(gameId: string): void {
|
||||
this.gameId = gameId;
|
||||
this.loadGame();
|
||||
@@ -94,6 +115,7 @@ export class GameFacade implements OnDestroy {
|
||||
}
|
||||
this.moveInput = '';
|
||||
this.clearSelection();
|
||||
this.tryMakeBotMove();
|
||||
},
|
||||
error: (error) => {
|
||||
this.errorMessage = getErrorMessage(error, 'Move rejected.');
|
||||
@@ -153,6 +175,7 @@ export class GameFacade implements OnDestroy {
|
||||
this.clearSelection();
|
||||
this.streamSubscription?.unsubscribe();
|
||||
this.pollSubscription?.unsubscribe();
|
||||
this.botMoveSubscription?.unsubscribe();
|
||||
this.pollSubscription = null;
|
||||
|
||||
this.gameApi
|
||||
@@ -163,6 +186,7 @@ export class GameFacade implements OnDestroy {
|
||||
this.game = game;
|
||||
this.loading = false;
|
||||
this.startStream();
|
||||
this.tryMakeBotMove();
|
||||
},
|
||||
error: (error) => {
|
||||
this.errorMessage = getErrorMessage(error, `Could not load game ${this.gameId}.`);
|
||||
@@ -205,6 +229,7 @@ export class GameFacade implements OnDestroy {
|
||||
this.game = game;
|
||||
if (previousMoves !== game.state.moves.join(',')) {
|
||||
this.clearSelection();
|
||||
this.tryMakeBotMove();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -214,6 +239,7 @@ export class GameFacade implements OnDestroy {
|
||||
if (event.type === 'gameFull') {
|
||||
this.game = event.game;
|
||||
this.clearSelection();
|
||||
this.tryMakeBotMove();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -222,6 +248,7 @@ export class GameFacade implements OnDestroy {
|
||||
this.game = { ...this.game, state: event.state };
|
||||
if (event.state.moves.length !== moveCountBefore) {
|
||||
this.clearSelection();
|
||||
this.tryMakeBotMove();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -231,6 +258,45 @@ export class GameFacade implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
private tryMakeBotMove(): void {
|
||||
if (!this.isPlayingAgainstBot() || !this.isCurrentPlayerBot() || !this.state) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.botMoveSubscription?.unsubscribe();
|
||||
this.botMoveSubscription = this.gameApi
|
||||
.getLegalMoves(this.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(this.gameId, botMove.uci)
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe({
|
||||
next: (state) => {
|
||||
if (this.game) {
|
||||
this.game = { ...this.game, state };
|
||||
}
|
||||
this.clearSelection();
|
||||
},
|
||||
error: (error) => {
|
||||
this.errorMessage = getErrorMessage(error, 'Bot move failed.');
|
||||
}
|
||||
});
|
||||
},
|
||||
error: () => {
|
||||
this.errorMessage = 'Could not get legal moves for bot move.';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private clearSelection(): void {
|
||||
this.selectedSquare = null;
|
||||
this.selectedSquareMoves = [];
|
||||
|
||||
Reference in New Issue
Block a user