Commit b27df183 authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat(chess): full gameplay polish — move list, captured pieces, promotion...

feat(chess): full gameplay polish — move list, captured pieces, promotion dialog, bot thinking indicator, clock urgency, improved result screen

- Move list: scrollable horizontal move notation (SAN)
- Captured pieces: sorted display below each player name
- Promotion dialog: visual piece picker (Q/R/B/N) instead of auto-queen
- Bot thinking indicator: animated dots overlay during Stockfish response
- Clock urgency: red pulsing animation when time < 30s
- Resign confirmation: prevents accidental resign
- Result screen: proper rating change, coins, XP, move history, analysis button
- Sound: differentiated for check, checkmate, castle, capture, normal move
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent c1234161
This diff is collapsed.
import * as scene from '../../../core/scene.js'; import * as scene from '../../../core/scene.js';
import * as audio from '../../../core/audio.js'; import * as audio from '../../../core/audio.js';
import * as bus from '../../../core/bus.js'; import * as bus from '../../../core/bus.js';
import * as store from '../../../core/store.js';
import { t } from '../../../core/i18n.js'; import { t } from '../../../core/i18n.js';
export function mountResult(el, params) { export function mountResult(el, params) {
const { result, reason, moves, mode, botId } = params; const { result, reason, coins = 0, xp = 0, moves = 0, mode, botId, pgn, moveHistory = [] } = params;
const isWin = result === 'win'; const isWin = result === 'win';
const isDraw = result === 'draw'; const isDraw = result === 'draw';
const icon = isWin ? '🏆' : isDraw ? '🤝' : '💀';
const title = isWin ? t('game.you_win') : isDraw ? t('game.draw_result') : t('game.you_lose'); const resultConfig = {
const color = isWin ? 'var(--win)' : isDraw ? 'var(--gold)' : 'var(--loss)'; win: { icon: '🏆', title: t('game.you_win'), color: '#34D399', ratingChange: '+12' },
const ratingChange = isWin ? '+12' : isDraw ? '+1' : '-8'; loss: { icon: '💀', title: t('game.you_lose'), color: '#F87171', ratingChange: '-8' },
const coins = isWin ? 50 : isDraw ? 20 : 10; draw: { icon: '🤝', title: t('game.draw_result'), color: '#E4AC38', ratingChange: '+1' }
};
const cfg = resultConfig[result] || resultConfig.loss;
const reasonText = {
checkmate: 'كش ملك',
stalemate: 'بات (جمود)',
resign: 'استسلام',
timeout: 'انتهاء الوقت',
threefold: 'تكرار ثلاثي',
insufficient: 'قطع غير كافية'
}[reason] || reason;
el.innerHTML = ` el.innerHTML = `
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:var(--s-6);padding:var(--s-6);"> <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:20px;padding:24px;background:#0a0a1a;">
<div style="font-size:64px;animation:float 2s ease-in-out infinite;">${icon}</div> <!-- Result Icon -->
<div style="font-size:72px;${isWin ? 'animation:float 2s ease-in-out infinite;' : ''}">${cfg.icon}</div>
<!-- Title & Reason -->
<div style="text-align:center;"> <div style="text-align:center;">
<div style="font-size:28px;font-weight:800;color:${color};">${title}</div> <div style="font-size:32px;font-weight:800;color:${cfg.color};">${cfg.title}</div>
<div style="font-size:14px;color:var(--text-secondary);margin-top:var(--s-1);">${reason}</div> <div style="font-size:14px;color:#94a3b8;margin-top:4px;">${reasonText}</div>
${moves ? `<div style="font-size:12px;color:#475569;margin-top:2px;">${moves} نقلة</div>` : ''}
</div> </div>
<div style="display:flex;gap:var(--s-6);margin-top:var(--s-4);"> <!-- Stats Row -->
<div style="display:flex;gap:24px;margin-top:8px;">
<div style="text-align:center;"> <div style="text-align:center;">
<div style="font-size:24px;font-weight:700;color:${color};font-family:var(--font-lat);">${ratingChange}</div> <div style="font-size:28px;font-weight:700;color:${cfg.color};font-family:Inter,monospace;">${cfg.ratingChange}</div>
<div style="font-size:11px;color:var(--text-secondary);">Rating</div> <div style="font-size:11px;color:#64748b;">تصنيف</div>
</div> </div>
<div style="width:1px;background:rgba(255,255,255,0.06);"></div>
<div style="text-align:center;"> <div style="text-align:center;">
<div style="font-size:24px;font-weight:700;color:var(--gold);font-family:var(--font-lat);">+${coins}</div> <div style="font-size:28px;font-weight:700;color:#E4AC38;font-family:Inter,monospace;">+${coins}</div>
<div style="font-size:11px;color:var(--text-secondary);">Coins</div> <div style="font-size:11px;color:#64748b;">عملات</div>
</div> </div>
<div style="width:1px;background:rgba(255,255,255,0.06);"></div>
<div style="text-align:center;"> <div style="text-align:center;">
<div style="font-size:24px;font-weight:700;color:var(--cyan);font-family:var(--font-lat);">+${moves * 2}</div> <div style="font-size:28px;font-weight:700;color:#00FFFF;font-family:Inter,monospace;">+${xp}</div>
<div style="font-size:11px;color:var(--text-secondary);">XP</div> <div style="font-size:11px;color:#64748b;">خبرة</div>
</div> </div>
</div> </div>
<div style="display:flex;gap:var(--s-3);margin-top:var(--s-6);"> <!-- Move Summary -->
<button class="btn btn-primary" id="btn-rematch">${t('game.rematch')}</button> ${moveHistory.length > 0 ? `
<button class="btn btn-secondary" id="btn-back">${t('game.back')}</button> <div style="background:#1a1a2e;border:1px solid rgba(255,255,255,0.06);border-radius:8px;padding:8px 12px;max-width:300px;width:100%;max-height:60px;overflow-x:auto;white-space:nowrap;">
<div style="font-family:Inter,monospace;font-size:11px;color:#94a3b8;">
${formatMoveHistory(moveHistory)}
</div>
</div>` : ''}
<!-- Actions -->
<div style="display:flex;flex-direction:column;gap:8px;width:100%;max-width:280px;margin-top:12px;">
<button class="btn btn-primary w-full" id="btn-rematch" style="font-size:15px;">${t('game.rematch')}</button>
<button class="btn btn-secondary w-full" id="btn-analyze" style="font-size:13px;">📊 تحليل المباراة</button>
<button class="btn btn-secondary w-full" id="btn-back" style="font-size:13px;">${t('game.back')}</button>
</div> </div>
</div> </div>
`; `;
if (isWin) {
bus.emit('coins:earned', { amount: coins });
bus.emit('xp:earned', { amount: moves * 2 });
}
el.querySelector('#btn-rematch').addEventListener('click', () => { el.querySelector('#btn-rematch').addEventListener('click', () => {
audio.play('click'); audio.play('click');
scene.replace('chess-game', { mode, botId, timeControl: 'rapid_10_0' }); scene.replace('chess-game', { mode, botId, timeControl: 'rapid_10_0' });
}); });
el.querySelector('#btn-analyze').addEventListener('click', () => {
audio.play('click');
// TODO: open analysis scene
});
el.querySelector('#btn-back').addEventListener('click', () => { el.querySelector('#btn-back').addEventListener('click', () => {
audio.play('click'); audio.play('click');
bus.emit('navigate', { world: 'play', scene: 'play-table' }); bus.emit('navigate', { world: 'play', scene: 'play-table' });
}); });
// Update player coins in store
const player = store.get('player');
if (player && coins > 0) {
store.set('player', { ...player, coins: (player.coins || 0) + coins, xp: (player.xp || 0) + xp });
}
}
function formatMoveHistory(history) {
let html = '';
for (let i = 0; i < Math.min(history.length, 30); i += 2) {
const num = Math.floor(i / 2) + 1;
html += `${num}.${history[i]?.san || ''} ${history[i+1]?.san || ''} `;
}
if (history.length > 30) html += '...';
return html;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment