Some checks failed
This commit addresses three critical bugs reported after initial PR: 1. **Promotion dialog not closing after piece selection** - Changed from `style.display` to HTML5 `.showModal()` and `.close()` - Fixed selector from `.promotion-piece .symbol` to `.promotion-piece .piece-icon` - Fixed data attribute from `dataset.type` to `dataset.piece` - Dialog now properly closes after user selects promotion piece 2. **Pawn showing as queen before user selection** - Removed automatic promotion to queen in GameController.js:112-115 - Now emits 'promotion' event WITHOUT pre-promoting - User sees pawn until they select the promotion piece - Promotion happens only after user makes their choice 3. **Column resizing not fully fixed** - Added explicit `max-width: 250px` to `.game-sidebar` and `.captured-pieces` - Added explicit `max-width: 250px` to `.move-history-section` - Added `overflow: hidden` to `.captured-list` and `overflow-x: hidden` to `.move-history` - Added `min-width: 600px` to `.board-section` - Added `width: 100%` to all sidebar components for proper constraint application - Columns now maintain stable widths even with content changes **Files Changed:** - `js/main.js` - Fixed promotion dialog handling - `js/controllers/GameController.js` - Removed auto-promotion - `css/main.css` - Added width constraints and overflow handling **Root Causes:** - Dialog: Mixing HTML5 dialog API with legacy display styles - Promotion: Auto-promoting before showing user dialog - Resizing: Missing explicit max-widths allowed flex items to grow with content 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
337 lines
9.8 KiB
JavaScript
337 lines
9.8 KiB
JavaScript
/**
|
|
* main.js - Application entry point
|
|
* Initializes game and connects all components
|
|
*/
|
|
|
|
import { GameController } from './controllers/GameController.js';
|
|
import { BoardRenderer } from './views/BoardRenderer.js';
|
|
import { DragDropHandler } from './controllers/DragDropHandler.js';
|
|
|
|
class ChessApp {
|
|
constructor() {
|
|
// Initialize components
|
|
this.game = new GameController({
|
|
autoSave: true,
|
|
enableTimer: false
|
|
});
|
|
|
|
this.renderer = new BoardRenderer(
|
|
document.getElementById('chess-board'),
|
|
{
|
|
showCoordinates: true,
|
|
pieceStyle: 'symbols',
|
|
highlightLastMove: true
|
|
}
|
|
);
|
|
|
|
this.dragDropHandler = new DragDropHandler(this.game, this.renderer);
|
|
|
|
// Initialize UI
|
|
this.initializeUI();
|
|
this.setupEventListeners();
|
|
this.setupGameEventListeners();
|
|
|
|
// Start new game
|
|
this.game.newGame();
|
|
this.updateDisplay();
|
|
}
|
|
|
|
/**
|
|
* Initialize UI components
|
|
*/
|
|
initializeUI() {
|
|
// Render initial board
|
|
this.renderer.renderBoard(this.game.board, this.game.gameState);
|
|
|
|
// Setup drag and drop
|
|
this.dragDropHandler.setupEventListeners();
|
|
|
|
// Update status
|
|
this.updateTurnIndicator();
|
|
}
|
|
|
|
/**
|
|
* Setup button event listeners
|
|
*/
|
|
setupEventListeners() {
|
|
// New Game
|
|
document.getElementById('btn-new-game').addEventListener('click', () => {
|
|
if (confirm('Start a new game? Current game will be lost.')) {
|
|
this.game.newGame();
|
|
this.updateDisplay();
|
|
this.showMessage('New game started!');
|
|
}
|
|
});
|
|
|
|
// Undo
|
|
document.getElementById('btn-undo').addEventListener('click', () => {
|
|
if (this.game.undo()) {
|
|
this.updateDisplay();
|
|
this.showMessage('Move undone');
|
|
} else {
|
|
this.showMessage('Nothing to undo');
|
|
}
|
|
});
|
|
|
|
// Redo
|
|
document.getElementById('btn-redo').addEventListener('click', () => {
|
|
if (this.game.redo()) {
|
|
this.updateDisplay();
|
|
this.showMessage('Move redone');
|
|
} else {
|
|
this.showMessage('Nothing to redo');
|
|
}
|
|
});
|
|
|
|
// Offer Draw
|
|
const offerDrawBtn = document.getElementById('btn-offer-draw');
|
|
if (offerDrawBtn) {
|
|
offerDrawBtn.addEventListener('click', () => {
|
|
this.game.offerDraw();
|
|
this.showMessage('Draw offered to opponent');
|
|
});
|
|
}
|
|
|
|
// Resign
|
|
document.getElementById('btn-resign').addEventListener('click', () => {
|
|
if (confirm('Are you sure you want to resign?')) {
|
|
this.game.resign();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Setup game event listeners
|
|
*/
|
|
setupGameEventListeners() {
|
|
// Move made
|
|
this.game.on('move', (data) => {
|
|
this.updateDisplay();
|
|
this.playMoveSound();
|
|
});
|
|
|
|
// Check
|
|
this.game.on('check', (data) => {
|
|
this.showMessage(`Check! ${data.color} king is in check`);
|
|
this.playCheckSound();
|
|
});
|
|
|
|
// Checkmate
|
|
this.game.on('checkmate', (data) => {
|
|
this.showMessage(`Checkmate! ${data.winner} wins!`, 'success');
|
|
this.dragDropHandler.disable();
|
|
this.playCheckmateSound();
|
|
});
|
|
|
|
// Stalemate
|
|
this.game.on('stalemate', () => {
|
|
this.showMessage('Stalemate! Game is a draw', 'info');
|
|
this.dragDropHandler.disable();
|
|
});
|
|
|
|
// Draw
|
|
this.game.on('draw', (data) => {
|
|
this.showMessage(`Draw by ${data.reason}`, 'info');
|
|
this.dragDropHandler.disable();
|
|
});
|
|
|
|
// Resign
|
|
this.game.on('resign', (data) => {
|
|
const winner = data.loser === 'white' ? 'Black' : 'White';
|
|
this.showMessage(`${data.loser} resigned. ${winner} wins!`, 'success');
|
|
this.dragDropHandler.disable();
|
|
});
|
|
|
|
// Promotion
|
|
this.game.on('promotion', (data) => {
|
|
this.showPromotionDialog(data.pawn, data.position);
|
|
});
|
|
|
|
// New Game
|
|
this.game.on('newgame', () => {
|
|
this.dragDropHandler.enable();
|
|
this.updateDisplay();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update all display elements
|
|
*/
|
|
updateDisplay() {
|
|
// Re-render board
|
|
this.renderer.renderBoard(this.game.board, this.game.gameState);
|
|
|
|
// Update turn indicator
|
|
this.updateTurnIndicator();
|
|
|
|
// Update move history
|
|
this.updateMoveHistory();
|
|
|
|
// Update captured pieces
|
|
this.updateCapturedPieces();
|
|
}
|
|
|
|
/**
|
|
* Update turn indicator
|
|
*/
|
|
updateTurnIndicator() {
|
|
const indicator = document.getElementById('current-turn');
|
|
if (!indicator) {
|
|
console.error('Turn indicator element not found');
|
|
return;
|
|
}
|
|
const turn = this.game.currentTurn;
|
|
indicator.textContent = `${turn.charAt(0).toUpperCase() + turn.slice(1)}'s Turn`;
|
|
indicator.style.color = turn === 'white' ? '#ffffff' : '#333333';
|
|
}
|
|
|
|
/**
|
|
* Update move history display
|
|
*/
|
|
updateMoveHistory() {
|
|
const moveList = document.getElementById('move-history');
|
|
const history = this.game.gameState.moveHistory;
|
|
|
|
if (history.length === 0) {
|
|
moveList.innerHTML = '<p style="color: #999; font-style: italic;">No moves yet</p>';
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
for (let i = 0; i < history.length; i += 2) {
|
|
const moveNumber = Math.floor(i / 2) + 1;
|
|
const whiteMove = history[i];
|
|
const blackMove = history[i + 1];
|
|
|
|
html += `<div>${moveNumber}. ${whiteMove.notation}`;
|
|
if (blackMove) {
|
|
html += ` ${blackMove.notation}`;
|
|
}
|
|
html += '</div>';
|
|
}
|
|
|
|
moveList.innerHTML = html;
|
|
moveList.scrollTop = moveList.scrollHeight;
|
|
}
|
|
|
|
/**
|
|
* Update captured pieces display
|
|
*/
|
|
updateCapturedPieces() {
|
|
const whiteCaptured = document.getElementById('captured-white-pieces');
|
|
const blackCaptured = document.getElementById('captured-black-pieces');
|
|
|
|
const captured = this.game.gameState.capturedPieces;
|
|
|
|
// "Captured by Black" shows white pieces that black captured
|
|
whiteCaptured.innerHTML = captured.white.map(piece =>
|
|
`<span class="captured-piece white">${piece.getSymbol()}</span>`
|
|
).join('') || '-';
|
|
|
|
// "Captured by White" shows black pieces that white captured
|
|
blackCaptured.innerHTML = captured.black.map(piece =>
|
|
`<span class="captured-piece black">${piece.getSymbol()}</span>`
|
|
).join('') || '-';
|
|
}
|
|
|
|
/**
|
|
* Show message to user
|
|
* @param {string} message - Message text
|
|
* @param {string} type - Message type (info, success, error)
|
|
*/
|
|
showMessage(message, type = 'info') {
|
|
const statusMessage = document.getElementById('status-message');
|
|
if (!statusMessage) {
|
|
console.warn('Status message element not found, using console:', message);
|
|
return;
|
|
}
|
|
|
|
// Add type class for styling
|
|
statusMessage.className = `status-message ${type}`;
|
|
statusMessage.textContent = message;
|
|
statusMessage.style.display = 'block';
|
|
|
|
// Auto-hide after 3 seconds
|
|
setTimeout(() => {
|
|
statusMessage.style.display = 'none';
|
|
}, 3000);
|
|
}
|
|
|
|
/**
|
|
* Show promotion dialog
|
|
* @param {Pawn} pawn - Pawn to promote
|
|
* @param {Position} position - Pawn position
|
|
*/
|
|
showPromotionDialog(pawn, position) {
|
|
const dialog = document.getElementById('promotion-dialog');
|
|
|
|
if (!dialog) {
|
|
console.error('Promotion dialog not found');
|
|
return;
|
|
}
|
|
|
|
// Show dialog using HTML5 dialog element API
|
|
dialog.showModal();
|
|
|
|
// Update symbols for current color
|
|
const symbols = pawn.color === 'white' ?
|
|
{ queen: '♕', rook: '♖', bishop: '♗', knight: '♘' } :
|
|
{ queen: '♛', rook: '♜', bishop: '♝', knight: '♞' };
|
|
|
|
document.querySelectorAll('.promotion-piece .piece-icon').forEach(el => {
|
|
const type = el.parentElement.dataset.piece;
|
|
el.textContent = symbols[type];
|
|
});
|
|
|
|
// Handle selection
|
|
const handleSelection = (e) => {
|
|
const pieceType = e.currentTarget.dataset.piece;
|
|
|
|
// Promote pawn
|
|
import('./engine/SpecialMoves.js').then(({ SpecialMoves }) => {
|
|
SpecialMoves.promote(this.game.board, pawn, pieceType);
|
|
this.updateDisplay();
|
|
});
|
|
|
|
// Close dialog using HTML5 dialog element API
|
|
dialog.close();
|
|
|
|
// Remove listeners
|
|
document.querySelectorAll('.promotion-piece').forEach(el => {
|
|
el.removeEventListener('click', handleSelection);
|
|
});
|
|
};
|
|
|
|
document.querySelectorAll('.promotion-piece').forEach(el => {
|
|
el.addEventListener('click', handleSelection);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Play move sound (optional - can be implemented)
|
|
*/
|
|
playMoveSound() {
|
|
// TODO: Add sound effect
|
|
}
|
|
|
|
/**
|
|
* Play check sound (optional - can be implemented)
|
|
*/
|
|
playCheckSound() {
|
|
// TODO: Add sound effect
|
|
}
|
|
|
|
/**
|
|
* Play checkmate sound (optional - can be implemented)
|
|
*/
|
|
playCheckmateSound() {
|
|
// TODO: Add sound effect
|
|
}
|
|
}
|
|
|
|
// Initialize app when DOM is ready
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.chessApp = new ChessApp();
|
|
console.log('Chess game initialized successfully!');
|
|
});
|