Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

better ai #87

Merged
merged 23 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
6 changes: 0 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,6 @@ jobs:
- name: Run ESLint
run: npm run lint

# Check bundle size
- name: Check bundle size
uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

deploy:
runs-on: ubuntu-latest
permissions:
Expand Down
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
"license": "MIT",
"type": "module",
"main": "dist/index.js",
"size-limit": [
{
"path": "dist/**/*.js",
"limit": "200 KB"
}
],
"scripts": {
"build": "vite build",
"start": "vite",
Expand Down
145 changes: 124 additions & 21 deletions src/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
centerControlBonus,
evaluateBoard,
evaluateKingSafety,
pieceValues,
} from './ai/evaluator';
import { getEndgameMove } from './ai/endgameTablebase';
import { flipMove, getNextOpeningMove, openingBook } from './ai/openingBook';
Expand All @@ -30,6 +31,7 @@ export class AI {
toX: number;
toY: number;
}[] = [];
private readonly historicalMoveScores: Map<string, number> = new Map(); // Stockage des scores historiques des mouvements

constructor(
private readonly color: PieceColor,
Expand All @@ -54,7 +56,7 @@ export class AI {

// Vérifie si un mouvement d'ouverture basé sur les coups passés est disponible
const pastMoves = this.getPastMoves();
const chosenMove = this.chooseMove(pastMoves);
const chosenMove = this.chooseMove(pastMoves, board);
if (chosenMove) {
this.moveHistory.push(chosenMove); // Ajoute à l'historique des coups
return chosenMove;
Expand Down Expand Up @@ -129,11 +131,24 @@ export class AI {
// Ajoute le meilleur mouvement trouvé à l'historique des coups si existant
if (bestMove) {
this.moveHistory.push(bestMove);
this.updateHistoricalScore(bestMove);
}

return bestMove;
}

// Fonction pour augmenter le score historique d'un mouvement après son utilisation
private updateHistoricalScore(move: {
fromX: number;
fromY: number;
toX: number;
toY: number;
}) {
const moveKey = `${move.fromX},${move.fromY},${move.toX},${move.toY}`;
const currentScore = this.historicalMoveScores.get(moveKey) || 0;
this.historicalMoveScores.set(moveKey, currentScore + 1);
}

// Fonction pour récupérer les coups passés en format abrégé
private getPastMoves(): string[] {
return this.moveHistory.map(
Expand Down Expand Up @@ -261,11 +276,35 @@ export class AI {

private chooseMove(
pastMoves: string[],
board: Board,
): { fromX: number; fromY: number; toX: number; toY: number } | null {
const openingMove = getNextOpeningMove(pastMoves, openingBook);
const openingMove = getNextOpeningMove(pastMoves, this.openingMoves);

if (openingMove) {
return openingMove;
const flippedMove = flipMove(
openingMove,
this.color === PieceColor.BLACK,
);

// Évaluation de la position pour ajuster le choix de l'ouverture
board.movePiece(
flippedMove.fromX,
flippedMove.fromY,
flippedMove.toX,
flippedMove.toY,
);
const evaluation = evaluateBoard(board, this.color);
board.setPiece(
flippedMove.fromX,
flippedMove.fromY,
board.getPiece(flippedMove.toX, flippedMove.toY),
);
board.setPiece(flippedMove.toX, flippedMove.toY, null);

const threshold = 0.3;
if (evaluation >= threshold) {
return flippedMove;
}
}

return null;
Expand Down Expand Up @@ -306,7 +345,7 @@ export class AI {
beta: number,
depth: number = 0,
): number {
const maxQuiescenceDepth = 5;
const maxQuiescenceDepth = this.getAdaptiveQuiescenceDepth(board);

if (depth >= maxQuiescenceDepth) {
return evaluateBoard(board, this.color);
Expand All @@ -320,7 +359,19 @@ export class AI {
board.isCapture(move.fromX, move.fromY, move.toX, move.toY),
);

for (const move of captureMoves) {
// Non-capture moves : Inclut quelques mouvements non capturants comme les poussées de pions
const nonCaptureMoves = this.getAllValidMoves(board).filter((move) => {
const piece = board.getPiece(move.fromX, move.fromY);
return (
!board.isCapture(move.fromX, move.fromY, move.toX, move.toY) &&
piece &&
(piece.type === PieceType.PAWN || piece.type === PieceType.KNIGHT)
);
});

const moves = [...captureMoves, ...nonCaptureMoves];

for (const move of moves) {
const fromPiece = board.getPiece(move.fromX, move.fromY);
const toPiece = board.getPiece(move.toX, move.toY);

Expand Down Expand Up @@ -395,29 +446,51 @@ export class AI {
depth: number,
): { fromX: number; fromY: number; toX: number; toY: number }[] {
return moves.sort((a, b) => {
const killerMovesAtDepth = this.killerMoves.get(depth);
// 1. Priorité aux mouvements capturant des pièces de valeur plus élevée
const pieceA = board.getPiece(a.toX, a.toY);
const pieceB = board.getPiece(b.toX, b.toY);

const valueA = pieceA ? pieceValues[pieceA.type] : 0;
const valueB = pieceB ? pieceValues[pieceB.type] : 0;

if (valueA !== valueB) {
return valueB - valueA; // Tri décroissant par valeur de capture
}

// 2. Bonus pour le contrôle des cases centrales
const centerControlA = centerControlBonus[`${a.toX},${a.toY}`] || 0;
const centerControlB = centerControlBonus[`${b.toX},${b.toY}`] || 0;

if (centerControlA !== centerControlB) {
return centerControlB - centerControlA; // Tri par contrôle du centre
}

// 3. Priorité aux mouvements dans les killer moves pour cette profondeur
const killerMovesAtDepth = this.killerMoves.get(depth);
if (
killerMovesAtDepth &&
killerMovesAtDepth.some(
(move: {
move: { fromX: number; fromY: number; toX: number; toY: number };
}) => move.move.fromX === a.fromX && move.move.fromY === a.fromY,
(move) =>
move.move.fromX === a.fromX &&
move.move.fromY === a.fromY &&
move.move.toX === a.toX &&
move.move.toY === a.toY,
)
) {
return -1;
}

const pieceA = board.getPiece(a.toX, a.toY);
const pieceB = board.getPiece(b.toX, b.toY);

if (pieceA && !pieceB) return -1;
if (!pieceA && pieceB) return 1;

const centerControlA = centerControlBonus[`${a.toX},${a.toY}`] || 0;
const centerControlB = centerControlBonus[`${b.toX},${b.toY}`] || 0;

return centerControlB - centerControlA;
// 4. Score historique pour le mouvement, favorise les coups réussis dans le passé
const scoreA =
this.historicalMoveScores.get(
`${a.fromX},${a.fromY},${a.toX},${a.toY}`,
) || 0;
const scoreB =
this.historicalMoveScores.get(
`${b.fromX},${b.fromY},${b.toX},${b.toY}`,
) || 0;

return scoreB - scoreA; // Tri par score historique décroissant
});
}

Expand Down Expand Up @@ -476,6 +549,14 @@ export class AI {
return { fromX, fromY, toX, toY };
}

// Adaptation de la profondeur de quiescence en fonction de la situation
private getAdaptiveQuiescenceDepth(board: Board): number {
const pieceCount = board.getPieceCount();
if (pieceCount <= 6) return 7; // Profondeur plus élevée en fin de partie
if (pieceCount <= 12) return 5; // Moyenne en milieu de partie
return 3; // Réduit en début de partie
}

private evaluatePositionWithKingSafety(
board: Board,
color: PieceColor,
Expand Down Expand Up @@ -582,11 +663,33 @@ export class AI {
const boardHash = this.getBoardHash(board);

if (this.openingMoves[boardHash]) {
// Sélectionne le premier coup suggéré par défaut
const move = this.openingMoves[boardHash][0];
return flipMove(move, this.color === PieceColor.BLACK); // Applique flipMove si c'est les Noirs
const flippedMove = flipMove(move, this.color === PieceColor.BLACK);

// Évalue la position après le coup d'ouverture
board.movePiece(
flippedMove.fromX,
flippedMove.fromY,
flippedMove.toX,
flippedMove.toY,
);
const evaluation = evaluateBoard(board, this.color);
board.setPiece(
flippedMove.fromX,
flippedMove.fromY,
board.getPiece(flippedMove.toX, flippedMove.toY),
);
board.setPiece(flippedMove.toX, flippedMove.toY, null);

// Seuil pour sortir du livre d'ouvertures si la position est défavorable
const exitThreshold = -0.5;
if (evaluation > exitThreshold) {
return flippedMove; // Choisit le coup d'ouverture par défaut si l'évaluation est favorable
}
}

return null;
return null; // Sort du livre si aucune ouverture valide n'est trouvée ou si l'évaluation est inférieure au seuil
}

// Génération d'un identifiant de position simplifié pour le dictionnaire d'ouverture
Expand Down
33 changes: 31 additions & 2 deletions src/ai/evaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Board } from '../board';
import { PieceColor, PieceType } from '../piece';

// Valeurs des pièces (évaluation de base)
const pieceValues: { [key in PieceType]: number } = {
export const pieceValues: { [key in PieceType]: number } = {
[PieceType.PAWN]: 1,
[PieceType.KNIGHT]: 3,
[PieceType.BISHOP]: 3.25,
Expand Down Expand Up @@ -136,9 +136,10 @@ export function evaluateBoard(
pieceScore += centerControlBonus[positionKey];
}

// Évalue les pions pour structure et bonus de pion passé
// Évalue les pions pour la structure et les chaînes protégées
if (piece.type === PieceType.PAWN) {
pieceScore += evaluatePawnStructure(board, x, y, piece.color);
pieceScore += evaluatePawnChains(board, x, y, piece.color); // Bonus pour chaînes de pions
}

// Pénalise les rois exposés
Expand All @@ -156,6 +157,34 @@ export function evaluateBoard(
return parseFloat(score.toFixed(2));
}

// Fonction pour évaluer les chaînes de pions
function evaluatePawnChains(
board: Board,
x: number,
y: number,
color: PieceColor,
): number {
const direction = color === PieceColor.WHITE ? -1 : 1;
let score = 0;

// Vérifie les pions sur les diagonales avant (chaînes protégées)
const leftDiagonal = board.getPiece(x - 1, y + direction);
const rightDiagonal = board.getPiece(x + 1, y + direction);

if (
(leftDiagonal &&
leftDiagonal.color === color &&
leftDiagonal.type === PieceType.PAWN) ||
(rightDiagonal &&
rightDiagonal.color === color &&
rightDiagonal.type === PieceType.PAWN)
) {
score += 0.5; // Bonus pour les pions protégés dans une chaîne
}

return score;
}

// Évaluer la structure des pions
function evaluatePawnStructure(
board: Board,
Expand Down