import { Game, CombinationStats, TypeChartStats, FullTypeChartCell } from './lib/game';
import { getTypeInfo, TypeName } from './lib/types';
import './style.css';
const app = document.getElementById('app')!;
let currentView: 'quiz' | 'stats' = 'quiz';
let typeChartView: 'attacking' | 'defending' = 'attacking';
const game = new Game(render);
game.nextQuestion();
function renderTypeBadge(name: TypeName, size: 'small' | 'large' = 'small'): string {
const info = getTypeInfo(name);
const sizeClass = size === 'large' ? 'type-badge-large' : '';
return `${info.name}`;
}
function render(): void {
if (currentView === 'stats') {
renderStatsPage();
return;
}
const state = game.getState();
const { options, startKey } = game.getOptions();
const stats = game.getStats();
if (!state.currentQuestion) {
app.innerHTML = '
Loading...
';
return;
}
const q = state.currentQuestion;
const defenderHtml = q.defenderTypes.length === 1
? renderTypeBadge(q.defenderTypes[0], 'large')
: `${renderTypeBadge(q.defenderTypes[0], 'large')} / ${renderTypeBadge(q.defenderTypes[1], 'large')}`;
const optionsHtml = state.showResult ? '' : options.map((opt, i) => {
let cls = 'option-btn';
if (i === state.selectedIndex) {
cls += ' selected';
}
const shortcut = `${startKey + i}`;
return `
`;
}).join('');
const resultHtml = state.showResult ? `
${state.isCorrect
? '
✓ Correct!
'
: `
✗ Wrong
${game.getExplanation()}
`
}
${!state.isCorrect ? '
' : ''}
` : '';
app.innerHTML = `
Attacking Type
${renderTypeBadge(q.attackType, 'large')}
against
Defending Type${q.defenderTypes.length > 1 ? 's' : ''}
${defenderHtml}
${optionsHtml}
${resultHtml}
`;
attachEventListeners();
}
function renderCombinationItem(combo: CombinationStats): string {
const defenderHtml = combo.defenderTypes.length === 1
? renderTypeBadge(combo.defenderTypes[0], 'small')
: `${renderTypeBadge(combo.defenderTypes[0], 'small')} / ${renderTypeBadge(combo.defenderTypes[1], 'small')}`;
const accuracyClass = combo.accuracy >= 80 ? 'high' : combo.accuracy >= 50 ? 'medium' : 'low';
return `
${renderTypeBadge(combo.attackType, 'small')}
vs
${defenderHtml}
${combo.accuracy.toFixed(0)}%
(${combo.correct}/${combo.total})
`;
}
function renderTypeChartCell(typeStat: TypeChartStats): string {
const info = getTypeInfo(typeStat.attackType);
const accuracyClass = typeStat.total === 0 ? 'no-data' : typeStat.accuracy >= 80 ? 'high' : typeStat.accuracy >= 50 ? 'medium' : 'low';
const display = typeStat.total === 0 ? '-' : `${typeStat.accuracy.toFixed(0)}%`;
return `
${typeStat.attackType.slice(0, 3)}
${display}
`;
}
function renderFullTypeChart(cells: FullTypeChartCell[]): string {
const defenderTypes = ['Normal', 'Fire', 'Water', 'Electric', 'Grass', 'Ice', 'Fighting', 'Poison', 'Ground', 'Flying', 'Psychic', 'Bug', 'Rock', 'Ghost', 'Dragon', 'Dark', 'Steel', 'Fairy'];
// Create a map for quick cell lookup
const cellMap = new Map();
for (const cell of cells) {
const defenderKey = cell.defenderTypes.join('/');
const key = `${cell.attackType}:${defenderKey}`;
cellMap.set(key, cell);
}
// Generate header row
const headerCells = defenderTypes.map(def => {
const info = getTypeInfo(def as TypeName);
return ``;
}).join('');
// Generate rows
const rows = defenderTypes.map(rowType => {
const rowInfo = getTypeInfo(rowType as TypeName);
const dataCells = defenderTypes.map(colType => {
const cell = cellMap.get(`${rowType}:${colType}`);
const total = cell?.total || 0;
const accuracy = cell?.accuracy || 0;
let bgColor = 'var(--bg-accent)';
let textColor = 'var(--text-secondary)';
let display = '-';
if (total > 0) {
display = `${accuracy.toFixed(0)}`;
if (accuracy >= 80) {
bgColor = 'rgba(74, 222, 128, 0.3)';
textColor = 'var(--success)';
} else if (accuracy >= 50) {
bgColor = 'rgba(251, 191, 36, 0.3)';
textColor = '#fbbf24';
} else {
bgColor = 'rgba(248, 113, 113, 0.3)';
textColor = 'var(--error)';
}
}
return `
${display}
`;
}).join('');
return `
${dataCells}
`;
}).join('');
return `
${rows}
`;
}
function renderStatsPage(): void {
const stats = game.getStats();
const bestCombos = game.getBestCombinations(5);
const worstCombos = game.getWorstCombinations(5);
const typeChartStats = typeChartView === 'attacking' ? game.getTypeChartStats() : game.getDefendingTypeStats();
const fullTypeChartCells = game.getFullTypeChartStats();
const hasData = stats.total > 0;
const bestCombosHtml = bestCombos.length > 0
? bestCombos.map(renderCombinationItem).join('')
: 'Answer at least 3 questions to see stats
';
const worstCombosHtml = worstCombos.length > 0
? worstCombos.map(renderCombinationItem).join('')
: 'Answer at least 3 questions to see stats
';
const typeChartHtml = typeChartStats.map(renderTypeChartCell).join('');
app.innerHTML = `
${stats.total}
Total Questions
${stats.accuracy.toFixed(1)}%
Accuracy
${hasData ? `
Best Combinations
Highest accuracy (min. 3 attempts)
${bestCombosHtml}
Needs Practice
Lowest accuracy (min. 3 attempts)
${worstCombosHtml}
Type Chart Performance
Accuracy by ${typeChartView} type
${typeChartHtml}
Full Type Chart
Accuracy for each type combination
${renderFullTypeChart(fullTypeChartCells)}
` : `
No data yet. Start playing to track your statistics!
`}
`;
document.getElementById('backBtn')?.addEventListener('click', () => {
currentView = 'quiz';
render();
});
document.querySelectorAll('.chart-toggle').forEach(btn => {
btn.addEventListener('click', () => {
const view = btn.getAttribute('data-view') as 'attacking' | 'defending';
typeChartView = view;
render();
});
});
}
function attachEventListeners(): void {
document.querySelectorAll('.option-btn').forEach(btn => {
btn.addEventListener('click', () => {
const index = parseInt(btn.getAttribute('data-index')!);
game.selectAnswer(index);
});
});
const dualToggle = document.getElementById('dualToggle') as HTMLInputElement;
if (dualToggle) {
dualToggle.addEventListener('change', () => {
game.setAllowDualTypes(dualToggle.checked);
});
}
const nextBtn = document.querySelector('.next-btn');
if (nextBtn) {
nextBtn.addEventListener('click', () => {
game.nextQuestion();
});
}
const statsBtn = document.getElementById('statsBtn');
if (statsBtn) {
statsBtn.addEventListener('click', () => {
currentView = 'stats';
render();
});
}
}
document.addEventListener('keydown', (e) => {
const state = game.getState();
const { options, startKey } = game.getOptions();
if (state.showResult) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
game.nextQuestion();
}
return;
}
const num = parseInt(e.key);
if (num >= startKey && num < startKey + options.length) {
e.preventDefault();
game.selectAnswer(num - startKey);
}
if (e.key === 'Enter') {
if (state.selectedAnswer !== null) {
e.preventDefault();
}
}
});
render();