Files
chess-project/scripts/testOpeningLookup.js
T
Stefan 878d469eb0 feat: Improve opening detection with hybrid exact + continuation approach
- Return exact match first (e.g., King's Pawn Game for 1.e4 e5)
- Fill remaining slots with common continuations (Ruy Lopez, Italian Game, etc.)
- Look up to 4 moves deeper to find important opening variations
- Prioritize by depth (closer continuations first), then ECO root flag, then ECO code
- Avoid duplicate ECO families (e.g., don't show multiple C6x openings)
- Add isEcoRoot field to OpeningMetadata interface
- Add test script to verify opening lookup logic

Example: After 1.e4 e5, now returns:
1. King's Pawn Game (C20) - exact match
2. King's Gambit (C30) - continuation
3. King's Knight Opening (C40) - continuation
4. Ruy Lopez (C60) - continuation
5. Italian Game (C50) - continuation

This gives users context about what openings are still possible.
2025-11-27 20:21:45 +01:00

94 lines
2.9 KiB
JavaScript

#!/usr/bin/env node
/**
* Test script for opening lookup functionality
* Tests the hybrid approach: exact matches + common continuations
*/
const moveIndex = require('../public/openings/moveIndex.json');
function lookupPossibleOpenings(moveSequence, maxResults = 5) {
const normalized = moveSequence.trim();
const results = [];
const seenEcoRoots = new Set();
// Step 1: Add exact matches first
const exactMatches = moveIndex[normalized] || [];
for (const opening of exactMatches) {
if (results.length >= maxResults) break;
results.push(opening);
const ecoRoot = opening.eco.substring(0, 2);
seenEcoRoots.add(ecoRoot);
}
// Step 2: If we haven't reached maxResults, look for common continuations
if (results.length < maxResults) {
const continuations = [];
for (const [seq, openings] of Object.entries(moveIndex)) {
if (seq.startsWith(normalized + ' ')) {
const ourPly = normalized.split(/\s+/).filter(s => s && !s.match(/^\d+\.$/)).length;
const theirPly = seq.split(/\s+/).filter(s => s && !s.match(/^\d+\.$/)).length;
const depth = theirPly - ourPly;
if (depth > 0 && depth <= 4) {
continuations.push({ sequence: seq, openings, depth });
}
}
}
continuations.sort((a, b) => {
if (a.depth !== b.depth) return a.depth - b.depth;
const aHasRoot = a.openings.some(o => o.isEcoRoot);
const bHasRoot = b.openings.some(o => o.isEcoRoot);
if (aHasRoot && !bHasRoot) return -1;
if (!aHasRoot && bHasRoot) return 1;
const aEco = a.openings[0]?.eco || 'ZZZ';
const bEco = b.openings[0]?.eco || 'ZZZ';
return aEco.localeCompare(bEco);
});
for (const { openings } of continuations) {
if (results.length >= maxResults) break;
const opening = openings[0];
if (opening) {
const ecoRoot = opening.eco.substring(0, 2);
if (!seenEcoRoots.has(ecoRoot)) {
results.push(opening);
seenEcoRoots.add(ecoRoot);
}
}
}
}
return results.slice(0, maxResults);
}
// Test cases
console.log('🧪 Testing Opening Lookup\n');
const testCases = [
'1. e4',
'1. e4 e5',
'1. e4 c5',
'1. d4',
'1. e4 e5 2. Nf3',
'1. e4 e5 2. Nf3 Nc6 3. Bb5'
];
for (const moveSeq of testCases) {
console.log(`\n📍 Move sequence: "${moveSeq}"`);
const openings = lookupPossibleOpenings(moveSeq, 5);
console.log(` Found ${openings.length} opening(s):`);
openings.forEach((o, i) => {
const type = moveIndex[moveSeq]?.includes(o) ? '[EXACT]' : '[CONTINUATION]';
console.log(` ${i + 1}. ${type} ${o.name} (${o.eco}) - ${o.moves}`);
});
}
console.log('\n✅ Test complete!\n');