This commit is contained in:
Stefan
2025-11-24 17:53:48 +01:00
parent b923f68841
commit 1dd54e8937
4 changed files with 69 additions and 32 deletions
-1
View File
@@ -1 +0,0 @@
404: Not Found
+3 -2
View File
@@ -199,12 +199,13 @@ export default function ChessGame({ initialFen, initialPgn, initialPersonality,
// Pre-Analysis (P0) // Pre-Analysis (P0)
useEffect(() => { useEffect(() => {
if (stockfish && gameRef.current.turn() === 'w' && !isAnalyzing && !gameOverState) { const playerTurn = playerColor === 'white' ? 'w' : 'b';
if (stockfish && gameRef.current.turn() === playerTurn && !isAnalyzing && !gameOverState) {
stockfish.evaluate(gameRef.current.fen(), stockfishDepth).then(evalResult => { stockfish.evaluate(gameRef.current.fen(), stockfishDepth).then(evalResult => {
setEvalP0(evalResult); setEvalP0(evalResult);
}).catch(err => console.error("Pre-analysis failed:", err)); }).catch(err => console.error("Pre-analysis failed:", err));
} }
}, [fen, stockfish, stockfishDepth, isAnalyzing, gameOverState]); }, [playerColor, fen, stockfish, stockfishDepth, isAnalyzing, gameOverState]);
const updateCapturedPieces = useCallback(() => { const updateCapturedPieces = useCallback(() => {
const history = gameRef.current.history({ verbose: true }); const history = gameRef.current.history({ verbose: true });
+5 -2
View File
@@ -38,19 +38,22 @@ export function GameAnalysisModal({ fen, stockfish, apiKey, language, onClose }:
// 3. LLM Summary // 3. LLM Summary
if (apiKey && evalResult) { if (apiKey && evalResult) {
const model = getGenAIModel(apiKey, "gemini-2.5-flash"); const model = getGenAIModel(apiKey, "gemini-2.5-flash");
const evalInPawns = (evalResult.score / 100).toFixed(2);
const prompt = ` const prompt = `
You are a Chess Grandmaster Analyst. You are a Chess Grandmaster Analyst.
Analyze this position for the user. Analyze this position for the user.
DATA: DATA:
- FEN: ${fen} - FEN: ${fen}
- Evaluation: ${evalResult.score} cp (positive = White advantage, negative = Black advantage) - Evaluation: ${evalInPawns} pawns (${evalResult.score} centipawns)
Note: Positive = White advantage, Negative = Black advantage
100 centipawns = 1 pawn
- Mate in: ${evalResult.mate ?? "N/A"} - Mate in: ${evalResult.mate ?? "N/A"}
- Best Move: ${evalResult.bestMove} - Best Move: ${evalResult.bestMove}
- Opening: ${openingData ? `${openingData.name} (${openingData.eco})` : "Unknown/Midgame"} - Opening: ${openingData ? `${openingData.name} (${openingData.eco})` : "Unknown/Midgame"}
INSTRUCTIONS: INSTRUCTIONS:
1. Summarize who is winning and why (based on score). 1. Summarize who is winning and why (based on score). Use the pawn value (e.g., "White is up 2.5 pawns" not "250 centipawns").
2. Identify the key strategic factors (space, piece activity, king safety). 2. Identify the key strategic factors (space, piece activity, king safety).
3. Mention the opening if relevant. 3. Mention the opening if relevant.
4. Keep it concise (max 3-4 sentences). 4. Keep it concise (max 3-4 sentences).
+61 -27
View File
@@ -10,6 +10,8 @@ export interface MoveHistoryItem {
evalBefore: number; // cp evalBefore: number; // cp
evalAfter: number; // cp evalAfter: number; // cp
bestMove?: string; bestMove?: string;
category?: 'inaccuracy' | 'mistake' | 'blunder';
cpLoss?: number;
} }
interface GameOverModalProps { interface GameOverModalProps {
@@ -31,41 +33,56 @@ export function GameOverModal({ result, winner, history, apiKey, language, onClo
const analyzeGame = async () => { const analyzeGame = async () => {
setIsLoading(true); setIsLoading(true);
try { try {
// 1. Identify Mistakes (Blunders) // 1. Identify Mistakes with proper categorization
// A blunder is roughly a drop of > 100cp (1 pawn) or missing a mate // Standard chess analysis thresholds:
const detectedMistakes = history.filter(item => { // - Inaccuracy: 50-100 centipawns loss
const delta = item.evalAfter - item.evalBefore; // - Mistake: 100-300 centipawns loss
// Note: eval is from White's perspective. // - Blunder: 300+ centipawns loss
// If White moves, eval should ideally go up or stay same. const detectedMistakes = history.map(item => {
// If eval drops significantly, it's a mistake. const delta = item.evalBefore - item.evalAfter; // Positive = eval got worse for player
return delta <= -100; let category: 'inaccuracy' | 'mistake' | 'blunder' | null = null;
});
if (delta >= 300) category = 'blunder';
else if (delta >= 100) category = 'mistake';
else if (delta >= 50) category = 'inaccuracy';
return { ...item, category, cpLoss: delta };
}).filter(item => item.category !== null) as MoveHistoryItem[];
setMistakes(detectedMistakes); setMistakes(detectedMistakes);
// 2. LLM Analysis // 2. LLM Analysis
if (apiKey) { if (apiKey) {
const model = getGenAIModel(apiKey, "gemini-2.5-flash"); const model = getGenAIModel(apiKey, "gemini-2.5-flash");
const blunders = detectedMistakes.filter(m => m.category === 'blunder');
const mistakes = detectedMistakes.filter(m => m.category === 'mistake');
const inaccuracies = detectedMistakes.filter(m => m.category === 'inaccuracy');
const mistakesText = detectedMistakes.map(m => const mistakesText = detectedMistakes.map(m =>
`Move ${m.moveNumber}: Played ${m.move} (Eval dropped from ${m.evalBefore} to ${m.evalAfter}). Best move was likely ${m.bestMove}.` `Move ${m.moveNumber}: ${m.move} (${m.category?.toUpperCase()}: -${m.cpLoss}cp, eval ${m.evalBefore} ${m.evalAfter}). Best: ${m.bestMove}`
).join("\n"); ).join("\n");
const prompt = ` const prompt = `
You are a Chess Coach. The game is over. You are a Chess Coach. The game is over.
Result: ${result} (${winner === "Draw" ? "Draw" : winner + " Won"}). Result: ${result} (${winner === "Draw" ? "Draw" : winner + " Won"}).
Here are the player's (White) key mistakes (Blunders): Player's Performance Summary:
${mistakesText || "No major blunders detected."} - Blunders (300+ cp loss): ${blunders.length}
- Mistakes (100-300 cp loss): ${mistakes.length}
- Inaccuracies (50-100 cp loss): ${inaccuracies.length}
${mistakesText ? `Detailed Mistakes:\n${mistakesText}` : "No significant mistakes detected - excellent play!"}
INSTRUCTIONS: INSTRUCTIONS:
1. Briefly comment on the game result. 1. Briefly comment on the game result.
2. If there were mistakes, explain WHY they were bad and what the player should have looked for (tactics, hanging pieces, etc.). 2. If there were mistakes, explain WHY the worst ones were bad and what the player should have looked for (tactics, hanging pieces, positional errors, etc.).
3. If no mistakes, praise the solid play. 3. If no mistakes, praise the solid play and suggest areas for improvement.
4. Be encouraging but educational. 4. Be encouraging but educational. Focus on learning.
5. Respond in ${language.toUpperCase()}. 5. Respond in ${language.toUpperCase()}.
OUTPUT FORMAT: OUTPUT FORMAT:
Plain text paragraph. Plain text paragraph (2-3 sentences).
`; `;
const resultGen = await model.generateContent(prompt); const resultGen = await model.generateContent(prompt);
@@ -112,18 +129,35 @@ Plain text paragraph.
Key Moments / Mistakes Key Moments / Mistakes
</h3> </h3>
<div className="max-h-40 overflow-y-auto space-y-2 pr-2"> <div className="max-h-40 overflow-y-auto space-y-2 pr-2">
{mistakes.map((m, idx) => ( {mistakes.map((m, idx) => {
<div key={idx} className="p-3 bg-orange-50 dark:bg-orange-900/10 border border-orange-100 dark:border-orange-900/30 rounded-lg text-sm"> const categoryColors = {
<span className="font-bold text-gray-900 dark:text-white">Move {m.moveNumber}: {m.move}</span> inaccuracy: 'bg-yellow-50 dark:bg-yellow-900/10 border-yellow-200 dark:border-yellow-900/30 text-yellow-700 dark:text-yellow-400',
<span className="mx-2 text-gray-400">|</span> mistake: 'bg-orange-50 dark:bg-orange-900/10 border-orange-200 dark:border-orange-900/30 text-orange-700 dark:text-orange-400',
<span className="text-red-600 dark:text-red-400">Eval: {m.evalBefore} {m.evalAfter}</span> blunder: 'bg-red-50 dark:bg-red-900/10 border-red-200 dark:border-red-900/30 text-red-700 dark:text-red-400'
{m.bestMove && ( };
<div className="text-gray-500 dark:text-gray-400 mt-1"> const categoryColor = categoryColors[m.category || 'inaccuracy'];
Best was likely: <span className="font-mono">{m.bestMove}</span>
return (
<div key={idx} className={`p-3 border rounded-lg text-sm ${categoryColor}`}>
<div className="flex items-center gap-2">
<span className="font-bold text-gray-900 dark:text-white">Move {m.moveNumber}: {m.move}</span>
<span className="px-2 py-0.5 rounded text-xs font-semibold uppercase bg-white/50 dark:bg-black/20">
{m.category}
</span>
</div> </div>
)} <div className="mt-1 text-xs">
</div> <span className="font-medium">Loss: -{m.cpLoss}cp</span>
))} <span className="mx-2 text-gray-400">|</span>
<span>Eval: {m.evalBefore} {m.evalAfter}</span>
</div>
{m.bestMove && (
<div className="text-gray-600 dark:text-gray-400 mt-1 text-xs">
Best: <span className="font-mono">{m.bestMove}</span>
</div>
)}
</div>
);
})}
</div> </div>
</div> </div>
)} )}