From c999c545a8c40917326984311f3f3b2e044b7aa2 Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Sun, 21 Jun 2026 20:53:13 +0200 Subject: [PATCH] rudimentary promotion selection --- assets/css/_promotion.css | 40 +++++++++++++++++++++ assets/css/app.css | 2 ++ assets/js/hooks/chessBoard.js | 66 +++++++++++++++++++++++++++++------ 3 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 assets/css/_promotion.css diff --git a/assets/css/_promotion.css b/assets/css/_promotion.css new file mode 100644 index 0000000..ef7f315 --- /dev/null +++ b/assets/css/_promotion.css @@ -0,0 +1,40 @@ +.promotion-overlay { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 100; + border-radius: inherit; +} +.promotion-picker { + display: flex; + flex-direction: column; + gap: 6px; + background: white; + padding: 10px; + border-radius: 14px; + box-shadow: 0 8px 32px rgba(0,0,0,0.35); +} +.promotion-piece { + width: 64px; + height: 64px; + display: flex; + align-items: center; + justify-content: center; + font-size: 42px; + cursor: pointer; + border: 2px solid transparent; + border-radius: 8px; + background: #fafafa; + transition: all 0.15s ease; +} +.promotion-piece:hover { + background: #eff6ff; + border-color: #3b82f6; + transform: scale(1.08); +} +.promotion-piece:active { + transform: scale(0.95); +} \ No newline at end of file diff --git a/assets/css/app.css b/assets/css/app.css index 370fdda..3650ca2 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -8,6 +8,8 @@ @import "../node_modules/chessground/assets/chessground.brown.css"; @import "../node_modules/chessground/assets/chessground.cburnett.css"; +@import "./_promotion.css"; + @source "../css"; @source "../js"; @source "../../lib/chesstrainer_web"; diff --git a/assets/js/hooks/chessBoard.js b/assets/js/hooks/chessBoard.js index c7b2fda..9af25b2 100644 --- a/assets/js/hooks/chessBoard.js +++ b/assets/js/hooks/chessBoard.js @@ -51,27 +51,71 @@ export const ChessBoard = { return dests; }, - handleMove(orig, dest) { - const { role, color } = this.ground.state.pieces.get(dest); - const destRank = dest.split("").pop() +// handleMove(orig, dest) { +// const { role, color } = this.ground.state.pieces.get(dest); +// const destRank = dest.split("").pop() - const isPromotion = this.isPromotion(role, destRank) +// const isPromotion = this.isPromotion(role, destRank) - if (isPromotion) { - console.log("PROMOTION LOGIC") - this.executeMove(orig, dest, "q"); - return +// if (isPromotion) { +// this.showPromotionDialog() +// this.executeMove(orig, dest, "q"); +// return +// } + +// this.executeMove(orig, dest, "q"); +// }, + +handleMove(orig, dest) { + const piece = this.ground.state.pieces.get(dest); + const destRank = dest[1]; + if (piece?.role === 'pawn' && (destRank === '1' || destRank === '8')) { + this.showPromotionDialog(orig, dest); + return; // don't execute yet — wait for user choice } - - this.executeMove(orig, dest, "q"); + this.executeMove(orig, dest); }, isPromotion(piece, rank) { return piece === "pawn" && (rank === "1" || rank === "8"); }, - showPromotionDialog() { + showPromotionDialog(orig, dest) { console.log("trigger promotion dialog"); + this.pendingPromotion = { orig, dest }; + const color = this.chess.turn() === 'w' ? 'white' : 'black'; + const overlay = document.createElement('div'); + overlay.className = 'promotion-overlay'; + const picker = document.createElement('div'); + picker.className = 'promotion-picker'; + const pieces = [ + { type: 'queen', symbol: { white: '♕', black: '♛' } }, + { type: 'rook', symbol: { white: '♖', black: '♜' } }, + { type: 'bishop', symbol: { white: '♗', black: '♝' } }, + { type: 'knight', symbol: { white: '♘', black: '♞' } }, + ]; + // chess.js promotion codes + const code = { queen: 'q', rook: 'r', bishop: 'b', knight: 'n' }; + pieces.forEach(({ type, symbol }) => { + const btn = document.createElement('button'); + btn.className = 'promotion-piece'; + btn.textContent = symbol[color]; + btn.addEventListener('click', () => this.completePromotion(code[type])); + picker.appendChild(btn); + }); + overlay.appendChild(picker); + const boardContainer = this.el.closest('.shadow-inner'); + boardContainer.style.position = 'relative'; + boardContainer.appendChild(overlay); + }, + + completePromotion(pieceType) { + const { orig, dest } = this.pendingPromotion; + this.pendingPromotion = null; + const picker = this.el.closest('.shadow-inner') + ?.querySelector('.promotion-overlay'); + if (picker) picker.remove(); + this.executeMove(orig, dest, pieceType); }, executeMove(orig, dest, promotion) {