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

Add tests #89

Merged
merged 32 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
8df52fb
Feature/save game (#65)
antoinegreuzard Oct 31, 2024
9f9a1f3
fix: remove saveGameToFile
antoinegreuzard Oct 31, 2024
b5db0b9
Update pull_request_template.md
antoinegreuzard Oct 31, 2024
a94d011
Merge pull request #75 from antoinegreuzard/antoinegreuzard-patch-2
antoinegreuzard Oct 31, 2024
d74e2be
fix: remove saveGameToFile
antoinegreuzard Nov 1, 2024
acdd779
refactor: add ai folder
antoinegreuzard Nov 1, 2024
29c8b6e
Create dependabot.yml
antoinegreuzard Nov 1, 2024
189dbd0
Merge pull request #79 from antoinegreuzard/antoinegreuzard-patch-3
antoinegreuzard Nov 1, 2024
ebc5b41
feat: add some jobs
antoinegreuzard Nov 1, 2024
d7df7ca
fix: add token
antoinegreuzard Nov 1, 2024
b793a3a
fix: rename token to github_token
antoinegreuzard Nov 1, 2024
2223919
feat: add letters and x,y on canvas
antoinegreuzard Nov 1, 2024
bc85dfa
remove dependabot
antoinegreuzard Nov 1, 2024
98a56a6
Merge branch 'main' into develop
antoinegreuzard Nov 1, 2024
656a93c
feat: add @size-limit
antoinegreuzard Nov 1, 2024
d20c571
remove size-limit
antoinegreuzard Nov 1, 2024
cfa0b35
remove size-limit
antoinegreuzard Nov 1, 2024
f399879
remove size-limit job
antoinegreuzard Nov 1, 2024
baa2ec6
Merge branch 'main' into develop
antoinegreuzard Nov 1, 2024
aeede2e
feat: better sort
antoinegreuzard Nov 1, 2024
89841be
feat: better quiescence
antoinegreuzard Nov 1, 2024
a8e2181
feat: better board evaluation
antoinegreuzard Nov 1, 2024
bcb289f
feat: better opening move
antoinegreuzard Nov 1, 2024
e6feb25
feat: add tests
antoinegreuzard Nov 1, 2024
5de891a
feat: add tests
antoinegreuzard Nov 1, 2024
2faa32b
feat: add tests
antoinegreuzard Nov 1, 2024
dcfa936
feat: add tests
antoinegreuzard Nov 1, 2024
a6f8182
feat: add tests
antoinegreuzard Nov 1, 2024
b450f68
feat: add tests
antoinegreuzard Nov 1, 2024
d56ac74
refactor
antoinegreuzard Nov 1, 2024
15ddbed
Merge branch 'main' into develop
antoinegreuzard Nov 1, 2024
a61ba7e
refactor
antoinegreuzard Nov 1, 2024
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
20 changes: 8 additions & 12 deletions src/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,9 @@ 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, board);
const chosenMove = this.chooseMove(board);
if (chosenMove) {
this.moveHistory.push(chosenMove); // Ajoute à l'historique des coups
this.moveHistory.push(chosenMove);
return chosenMove;
}
const endgameMove = this.useEndgameTablebase(board);
Expand Down Expand Up @@ -149,13 +148,6 @@ export class AI {
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(
(move) => `${move.fromX}${move.fromY}${move.toX}${move.toY}`,
);
}

// Fonction Minimax avec Alpha-Beta Pruning et table de transposition
private minimax(
board: Board,
Expand Down Expand Up @@ -275,10 +267,14 @@ export class AI {
}

private chooseMove(
pastMoves: string[],
board: Board,
): { fromX: number; fromY: number; toX: number; toY: number } | null {
const openingMove = getNextOpeningMove(pastMoves, this.openingMoves);
const key = this.moveHistory
.map((move) => `${move.fromX}${move.fromY}${move.toX}${move.toY}`)
.join(' ');

console.log(key);
const openingMove = getNextOpeningMove(key, this.openingMoves);

if (openingMove) {
const flippedMove = flipMove(
Expand Down
102 changes: 63 additions & 39 deletions src/ai/evaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,19 @@ function getPieceSquareValue(
x: number,
y: number,
flipBoard: boolean,
board: Board,
color: PieceColor,
): number {
const table = pieceSquareTables[type];
if (!table) return 0;

// Inversion de la table pour les pièces noires si flipBoard est activé
// Empêcher les pions isolés de recevoir un bonus de position
if (type === PieceType.PAWN) {
const isIsolated = checkIsolatedPawns(board, x, y, color) > 0;
return isIsolated ? 0 : flipBoard ? table[7 - y][7 - x] : table[y][x];
}

// Retourne la valeur de position pour les autres pièces
return flipBoard ? table[7 - y][7 - x] : table[y][x];
}

Expand All @@ -124,16 +132,28 @@ export function evaluateBoard(
for (let y = 0; y < 8; y++) {
for (let x = 0; x < 8; x++) {
const piece = board.getPiece(x, y);
const positionKey = `${x},${y}`;
if (!piece) continue;

// Applique la valeur de base et la table de position
let pieceScore = pieceValues[piece.type];
pieceScore += getPieceSquareValue(piece.type, x, y, flipBoard);
pieceScore += getPieceSquareValue(
piece.type,
x,
y,
flipBoard,
board,
piece.color,
);

// Ajoute le bonus pour le contrôle du centre
const positionKey = `${x},${y}`;
if (centerControlBonus[positionKey]) {
pieceScore += centerControlBonus[positionKey];
if (
piece.type === PieceType.PAWN &&
checkIsolatedPawns(board, x, y, piece.color) === 0
) {
if (centerControlBonus[positionKey]) {
pieceScore += centerControlBonus[positionKey];
}
}

// Évalue les pions pour la structure et les chaînes protégées
Expand Down Expand Up @@ -185,7 +205,6 @@ function evaluatePawnChains(
return score;
}

// Évaluer la structure des pions
function evaluatePawnStructure(
board: Board,
x: number,
Expand All @@ -194,17 +213,22 @@ function evaluatePawnStructure(
): number {
let score = 0;

// Pénalise les pions doublés
score -= checkDoubledPawns(board, x, y, color) * 1.5;

// Pénalise les pions isolés
score -= checkIsolatedPawns(board, x, y, color) * 1.5;
const isPassed = isPassedPawn(board, x, y, color);
const doubledPenalty = checkDoubledPawns(board, x, y, color) * 0.25;
const isolatedPenalty = checkIsolatedPawns(board, x, y, color) * 4.0;

// Bonus pour les pions passés
if (isPassedPawn(board, x, y, color)) {
score += 1.0;
if (isPassed) {
score += 4.5; // Bonus pour pion passé
console.log(
`Passed Pawn at (${x},${y}) | Passed bonus: 4.5, Score: ${score}`,
);
}

score -= doubledPenalty + isolatedPenalty;
console.log(
`Pawn at (${x},${y}) | Doubled penalty: ${doubledPenalty}, Isolated penalty: ${isolatedPenalty}, Score: ${score}`,
);

return score;
}

Expand Down Expand Up @@ -235,18 +259,15 @@ function checkIsolatedPawns(
const leftColumn = x - 1 >= 0 ? board.getPiece(x - 1, y) : null;
const rightColumn = x + 1 < 8 ? board.getPiece(x + 1, y) : null;

if (
(!leftColumn ||
leftColumn.type !== PieceType.PAWN ||
leftColumn.color !== color) &&
(!rightColumn ||
rightColumn.type !== PieceType.PAWN ||
rightColumn.color !== color)
) {
return 1.5; // Augmentation de la pénalité pour les pions isolés
}
const hasAdjacentSameColorPawns =
(leftColumn &&
leftColumn.type === PieceType.PAWN &&
leftColumn.color === color) ||
(rightColumn &&
rightColumn.type === PieceType.PAWN &&
rightColumn.color === color);

return 0;
return hasAdjacentSameColorPawns ? 0 : 1.5; // Retourne une pénalité si le pion est isolé
}

// Gère les cases hors limites pour éviter des expositions de roi mal calculées
Expand Down Expand Up @@ -291,8 +312,9 @@ function isPassedPawn(
y: number,
color: PieceColor,
): boolean {
const direction = color === PieceColor.WHITE ? -1 : 1;
const direction = color === PieceColor.WHITE ? 1 : -1;

// Vérifie s'il y a des pions adverses devant le pion sur la même colonne
for (let i = y + direction; i >= 0 && i < 8; i += direction) {
const pieceInFront = board.getPiece(x, i);
if (
Expand All @@ -304,20 +326,22 @@ function isPassedPawn(
}
}

// Vérifier s'il y a des pions alliés sur les colonnes adjacentes
// Vérifie les colonnes adjacentes pour s'assurer qu'il n'y a pas de pions adverses bloquant
const adjacentColumns = [x - 1, x + 1];
return adjacentColumns.every((col) => {
if (col < 0 || col >= 8) return true;
for (let i = 0; i < 8; i++) {
const adjacentPiece = board.getPiece(col, i);
if (
adjacentPiece &&
adjacentPiece.type === PieceType.PAWN &&
adjacentPiece.color === color
) {
return false;
for (const col of adjacentColumns) {
if (col >= 0 && col < 8) {
for (let i = y + direction; i >= 0 && i < 8; i += direction) {
const adjacentPiece = board.getPiece(col, i);
if (
adjacentPiece &&
adjacentPiece.type === PieceType.PAWN &&
adjacentPiece.color !== color
) {
return false;
}
}
}
return true;
});
}

return true;
}
5 changes: 2 additions & 3 deletions src/ai/openingBook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,10 @@ export function flipMove(
}

export function getNextOpeningMove(
moves: string[],
key: string,
openingBook: OpeningBook,
): { fromX: number; fromY: number; toX: number; toY: number } | null {
const key = moves.join(' ');
return openingBook[key]?.[moves.length] ?? null;
return openingBook[key]?.[0] ?? null;
}

export type OpeningBook = {
Expand Down
26 changes: 23 additions & 3 deletions tests/endgameTablebase.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('Endgame Move Generation Tests', () => {

beforeEach(async () => {
board = new Board();
await board.init(); // Initialise un échiquier vide pour chaque test
await board.init();
board['grid'] = Array(8).fill(null).map(() => Array(8).fill(null));
});

Expand All @@ -38,7 +38,7 @@ describe('Endgame Move Generation Tests', () => {

const move = getEndgameMove(board, PieceColor.WHITE);
expect(move).not.toBeNull();
expect(move?.fromX).toBe(2); // Cavalier se déplace pour pousser le roi adverse
expect(move?.fromX).toBe(2); // Fou se déplace pour rapprocher le roi adverse
});

test('King + Two Bishops vs King endgame move', async () => {
Expand All @@ -49,7 +49,7 @@ describe('Endgame Move Generation Tests', () => {

const move = getEndgameMove(board, PieceColor.WHITE);
expect(move).not.toBeNull();
expect(move?.fromX).toBe(2); // Fou se déplace pour forcer le roi adverse vers un coin
expect(move?.fromX).toBe(2); // Fou pousse le roi adverse vers le bord
});

test('King + Pawn vs King endgame move', async () => {
Expand All @@ -63,4 +63,24 @@ describe('Endgame Move Generation Tests', () => {
expect(move?.fromY).toBe(5);
expect(move?.toY).toBe(6); // Pion avance pour promotion
});

test('No endgame move available when pieces do not match any endgame scenario', async () => {
await placePiece(board, PieceType.KING, PieceColor.WHITE, 4, 4);
await placePiece(board, PieceType.ROOK, PieceColor.WHITE, 5, 5);
await placePiece(board, PieceType.BISHOP, PieceColor.WHITE, 2, 2);
await placePiece(board, PieceType.KING, PieceColor.BLACK, 7, 7);

const move = getEndgameMove(board, PieceColor.WHITE);
expect(move).toBeNull(); // Aucun mouvement optimal pour cette configuration
});

test('Endgame move generation considers flipBoard for black pieces', async () => {
await placePiece(board, PieceType.KING, PieceColor.BLACK, 0, 0);
await placePiece(board, PieceType.ROOK, PieceColor.BLACK, 1, 0);
await placePiece(board, PieceType.KING, PieceColor.WHITE, 7, 7);

const move = getEndgameMove(board, PieceColor.BLACK, true);
expect(move).not.toBeNull();
expect(move?.fromX).toBe(6); // La position est inversée pour la vue noire
});
});
33 changes: 26 additions & 7 deletions tests/evaluateBoard.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// evaluateBoard.test.ts
// tests/evaluateBoard.test.ts
import { Board } from '../src/board';
import { PieceColor } from '../src/piece';
import { Pawn } from '../src/pieces/pawn';
Expand Down Expand Up @@ -30,22 +30,22 @@ describe('evaluateBoard', () => {
board.setPiece(4, 4, new Pawn(PieceColor.WHITE));
board.setPiece(4, 3, new Pawn(PieceColor.BLACK));
const score = evaluateBoard(board, PieceColor.WHITE);
expect(score).toBeGreaterThan(-0.1); // Ajustement du seuil attendu
expect(score).toBeGreaterThan(-0.1); // Seuil attendu pour les pions
});

it('should evaluate material advantage correctly', () => {
board.setPiece(0, 0, new Queen(PieceColor.WHITE));
board.setPiece(7, 7, new Rook(PieceColor.BLACK));
const score = evaluateBoard(board, PieceColor.WHITE);
expect(score).toBeCloseTo(3.8, 1); // Ajustement de la précision à 1
expect(score).toBeCloseTo(3.8, 1); // Précision d'une décimale
});

it('should consider pawn structure (doubled and isolated pawns)', () => {
board.setPiece(0, 1, new Pawn(PieceColor.WHITE));
board.setPiece(0, 2, new Pawn(PieceColor.WHITE));
board.setPiece(2, 2, new Pawn(PieceColor.WHITE)); // pion isolé
board.setPiece(0, 2, new Pawn(PieceColor.WHITE)); // Pions doublés
board.setPiece(2, 2, new Pawn(PieceColor.WHITE)); // Pion isolé
const score = evaluateBoard(board, PieceColor.WHITE);
expect(score).toBeLessThan(0); // Structure de pions affaiblie
expect(score).toBeLessThan(0); // Pénalisation pour structure faible
});

it('should give a bonus for a bishop pair', () => {
Expand All @@ -62,12 +62,31 @@ describe('evaluateBoard', () => {
});

it('should apply center control bonus', () => {
board.setPiece(3, 3, new Knight(PieceColor.WHITE)); // Contrôle centre
board.setPiece(3, 3, new Knight(PieceColor.WHITE)); // Contrôle du centre
const score = evaluateBoard(board, PieceColor.WHITE);
expect(score).toBeGreaterThan(0); // Bonus pour contrôle du centre
});

it('should return 0 for an empty board', () => {
expect(evaluateBoard(board, PieceColor.WHITE)).toBe(0);
});

it('should apply penalty for doubled pawns', () => {
board.setPiece(4, 4, new Pawn(PieceColor.WHITE));
board.setPiece(4, 5, new Pawn(PieceColor.WHITE)); // Pions doublés sur la colonne 4
const score = evaluateBoard(board, PieceColor.WHITE);
expect(score).toBeLessThan(0); // Pénalisation pour pions doublés
});

it('should penalize isolated pawn', () => {
board.setPiece(3, 3, new Pawn(PieceColor.WHITE));
const score = evaluateBoard(board, PieceColor.WHITE);
expect(score).toBeLessThan(0); // Pénalisation pour pion isolé
});

it('should apply position value for knight', () => {
board.setPiece(2, 2, new Knight(PieceColor.WHITE));
const score = evaluateBoard(board, PieceColor.WHITE);
expect(score).toBeGreaterThan(3); // Inclut la position favorable du cavalier
});
});
Loading