Skip to content

Instantly share code, notes, and snippets.

@danbri
Created January 15, 2026 21:27
Show Gist options
  • Select an option

  • Save danbri/a6b0d5b4457b9eb76d728430dd8d9c96 to your computer and use it in GitHub Desktop.

Select an option

Save danbri/a6b0d5b4457b9eb76d728430dd8d9c96 to your computer and use it in GitHub Desktop.
mamichess! πŸ‘‘
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mamichess</title>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
padding-top: 20px;
min-height: 100vh;
margin: 0;
background-color: #333;
color: #eee;
font-family: 'Segoe UI Emoji', 'Apple Color Emoji', 'Segoe UI Symbol', 'Noto Color Emoji', Arial, sans-serif;
user-select: none;
overflow: hidden; /* Prevent scrollbars during full-screen animation */
}
h1 { margin-bottom: 15px; color: #fff; text-transform: uppercase; letter-spacing: 1px; }
#game-container {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
z-index: 1;
}
#game-board {
display: grid;
grid-template-columns: repeat(4, 75px);
grid-template-rows: repeat(3, 75px);
border: 5px solid #555;
box-shadow: 0 0 15px rgba(0,0,0,0.3);
border-radius: 5px;
position: relative;
}
.cell {
width: 75px;
height: 75px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
position: relative;
transition: background-color 0.2s ease-out, box-shadow 0.2s ease-out;
}
.cell.light-square { background-color: #e0c4a0; }
.cell.dark-square { background-color: #a07c54; }
.piece-emoji {
display: inline-flex;
align-items: center;
justify-content: center;
transition: opacity 0.1s, transform 0.1s;
font-size: 68px;
line-height: 1;
}
.piece-emoji[data-piece="king"],
.piece-emoji[data-piece="queen"] {
font-size: 102px; /* 68px * 1.5 */
}
.piece-emoji.dragging { opacity: 0.3; }
.piece-emoji.active-piece-animation {
animation: gentle-throb 1.2s infinite ease-in-out alternate;
}
@keyframes gentle-throb {
from { transform: scale(1); opacity: 1; }
to { transform: scale(1.08); opacity: 0.9; }
}
#controls { margin-top: 20px; display: flex; flex-direction: column; align-items: center; }
#message { font-size: 1.1em; font-weight: bold; color: #f0f0f0; text-align: center; margin-bottom:12px; min-height: 1.3em;}
#subtle-move-message { font-size: 0.9em; color: #a0a0ff; text-align: center; min-height: 1em; margin-bottom: 5px;}
#buttons-panel { display: flex; gap: 10px; }
.control-button, #help-toggle-label { /* Keeping #help-toggle-label for potential future use if button becomes a label */
padding: 10px 15px; font-size: 1.2em; cursor: pointer; border: 1px solid #777;
background-color: #555; color: #fff; border-radius: 5px;
transition: background-color 0.2s, box-shadow 0.2s;
display: inline-flex; align-items: center; justify-content: center; min-width: 50px;
}
.control-button:hover, #help-toggle-label:hover { background-color: #666; box-shadow: 0 0 5px rgba(255,255,255,0.2); }
#play-again { display: none; }
/* #help-toggle { display: none; } /* This was for a checkbox, not used. */
#explanation-btn.active {
background-color: #779977; border-color: #99bb99;
}
#explanation-text {
display:none;
margin-top:15px;
padding:12px;
border:1px solid #777;
background-color:#444;
max-width: 320px; /* Constrain width */
text-align:left;
font-size: 1em;
line-height: 1.6;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
color: #ddd;
/* === FIX FOR SCROLLABILITY === */
max-height: 35vh; /* Limit height (e.g., 35% of viewport height or a fixed px value like 250px) */
overflow-y: auto; /* Add scrollbar if content exceeds max-height */
position: relative; /* Ensure it layers correctly if needed, though likely not critical here */
z-index: 10; /* Same as above */
}
#explanation-text a { color: #8af; text-decoration: none; }
#explanation-text a:hover { text-decoration: underline; }
#explanation-text ul {
list-style: disc;
padding-left: 25px;
margin-top: 8px;
margin-bottom: 0;
}
#explanation-text li {
margin-bottom: 8px;
}
#explanation-text li:last-child {
margin-bottom: 0;
}
/* --- Skull End Game Animation Styles --- */
#skull-animation-overlay {
display: none;
position: fixed;
top: 0; left: 0;
width: 100vw; height: 100vh;
background-color: rgba(0,0,0,0);
z-index: 2000;
pointer-events: none;
}
#animated-skull {
position: absolute;
opacity: 0;
color: #fff;
text-align: center;
will-change: transform, opacity, font-size, left, top;
transform-origin: center center;
}
.king-on-skull-fading {
animation: fadeOutBoardPiece 0.5s ease-out forwards;
}
@keyframes fadeOutBoardPiece {
from { opacity: 1; transform: scale(1); }
to { opacity: 0; transform: scale(0.7); }
}
#animated-skull.trigger-skull-win-animation {
animation:
skullInitialFadeIn 0.3s forwards,
skullGlowRed 1.5s linear infinite 0.1s,
skullGrowAndCenter2x 2.0s cubic-bezier(0.4, 0, 0.2, 1) 0.1s forwards;
}
@keyframes skullInitialFadeIn {
to { opacity: 1; }
}
@keyframes skullGlowRed {
0%, 100% { text-shadow: 0 0 10px #ff0000, 0 0 20px #ff0000, 0 0 30px #ff0000; }
50% { text-shadow: 0 0 15px #ff3333, 0 0 25px #ff3333, 0 0 35px #ff3333; }
}
@keyframes skullGrowAndCenter2x {
0% {
transform: translate(0,0) scale(1);
opacity: 1;
}
40% {
left: 50vw;
top: 50vh;
transform: translate(-50%, -50%) scale(1.5);
opacity: 1;
}
100% {
left: 50vw;
top: 50vh;
transform: translate(-50%, -50%) scale(2);
opacity: 0.9;
}
}
#skull-animation-overlay.fading-to-black {
animation: fadeToBlackBG 1s forwards;
}
@keyframes fadeToBlackBG {
to { background-color: rgba(0,0,0,1); }
}
#skull-animation-overlay.fading-out-from-black {
animation: fadeOutBlackBG 1s forwards;
}
@keyframes fadeOutBlackBG {
from { background-color: rgba(0,0,0,1); opacity: 1; }
to { background-color: rgba(0,0,0,0); opacity: 0; }
}
</style>
</head>
<body>
<div id="game-container">
<div id="game-board"></div>
<div id="controls">
<p id="message"></p>
<p id="subtle-move-message"></p>
<div id="buttons-panel">
<button id="explanation-btn" class="control-button" title="Help/Rules" aria-controls="explanation-text" aria-expanded="false">?</button>
<button id="play-again" class="control-button" title="Play Again">πŸ”„</button>
</div>
<div id="explanation-text"> <!-- Restored to original position -->
<strong>Mamichess Puzzle Prime</strong><br>
The goal for the Queen (β™•) is to checkmate the opposing King (β™š) by forcing it onto the "grave" square (πŸ’€, d3: the top-right square). Queen moves first. The King will try its best to avoid the grave or capture the Queen. Queen has a forced win.
<br><br>
<strong>Learn More:</strong>
<ul>
<li>About the creator: <a href="https://en.wikipedia.org/wiki/Mamikon_Mnatsakanian" target="_blank" rel="noopener noreferrer">Mamikon Mnatsakanian</a> (Wikipedia)</li>
<li>Mamikon's work (including this puzzle): <a href="https://web.archive.org/web/20080307094905/https:/home.earthlink.net/~mamikara/Start.html" target="_blank" rel="noopener noreferrer">Mamikon's Visual Calculus Page</a></li>
<li>Background and solutions: <a href="https://web.archive.org/web/20080406051404/http://www.mamikon.com/GARDNER/MamikonMiniChess.html" target="_blank" rel="noopener noreferrer">Mamikon Mini-Chess page</a>.</li>
</ul>
<p></p>
</div>
</div>
</div>
<div id="skull-animation-overlay">
<span id="animated-skull" class="piece-emoji">πŸ’€</span>
</div>
<script>
const BOARD_SIZE = { rows: 3, cols: 4 };
const PIECE_EMOJI = { WHITE_QUEEN: 'β™•', BLACK_KING: 'β™š', SKULL: 'πŸ’€' };
const INITIAL_KING_POS = { row: 0, col: 0 };
const INITIAL_QUEEN_POS = { row: 1, col: 2 };
const SKULL_TARGET = { row: 0, col: 3 };
let kingPos, queenPos;
let currentTurn = 'white';
let gameOver = false;
let queenPossibleMoves = [];
let cellElements = [];
let draggedPieceElement = null;
let originalQueenPos = null;
let isAnimatingMove = false;
const ANIMATION_DURATION_MS = 350;
const boardElement = document.getElementById('game-board');
const messageElement = document.getElementById('message');
const subtleMoveMessageElement = document.getElementById('subtle-move-message');
const playAgainButton = document.getElementById('play-again');
const explanationBtn = document.getElementById('explanation-btn');
const explanationText = document.getElementById('explanation-text'); // Restored
const skullAnimationOverlay = document.getElementById('skull-animation-overlay');
const animatedSkullElement = document.getElementById('animated-skull');
function createBoard() {
boardElement.innerHTML = '';
cellElements = [];
for (let r = 0; r < BOARD_SIZE.rows; r++) {
for (let c = 0; c < BOARD_SIZE.cols; c++) {
const cell = document.createElement('div');
cell.classList.add('cell');
cell.dataset.row = r;
cell.dataset.col = c;
if ((r + c) % 2 === 0) cell.classList.add('light-square');
else cell.classList.add('dark-square');
cell.addEventListener('click', handleCellClick);
cell.addEventListener('dragover', handleDragOver);
cell.addEventListener('dragenter', handleDragEnterCell);
cell.addEventListener('dragleave', handleDragLeaveCell);
cell.addEventListener('drop', handleDrop);
boardElement.appendChild(cell);
cellElements.push(cell);
}
}
}
function getCellElement(row, col) {
if (row < 0 || row >= BOARD_SIZE.rows || col < 0 || col >= BOARD_SIZE.cols) return null;
return cellElements[row * BOARD_SIZE.cols + col];
}
function drawPiecesAndHighlights() {
if (currentTurn === 'white' && !gameOver && !isAnimatingMove) {
queenPossibleMoves = getValidMovesForPiece(queenPos, 'queen');
} else {
queenPossibleMoves = [];
}
subtleMoveMessageElement.textContent = '';
cellElements.forEach(cell => {
cell.innerHTML = '';
const r = parseInt(cell.dataset.row);
const c = parseInt(cell.dataset.col);
let pieceSpan = null;
if (r === SKULL_TARGET.row && c === SKULL_TARGET.col) {
const isSkullCoveredByPiece = (kingPos.row === r && kingPos.col === c) || (queenPos.row === r && queenPos.col === c);
if (!isSkullCoveredByPiece) {
pieceSpan = document.createElement('span');
pieceSpan.classList.add('piece-emoji');
pieceSpan.textContent = PIECE_EMOJI.SKULL;
}
}
if (r === queenPos.row && c === queenPos.col) {
pieceSpan = document.createElement('span');
pieceSpan.classList.add('piece-emoji');
pieceSpan.textContent = PIECE_EMOJI.WHITE_QUEEN;
pieceSpan.dataset.piece = 'queen';
if (currentTurn === 'white' && !gameOver) {
pieceSpan.classList.add('active-piece-animation');
if (!isAnimatingMove) {
pieceSpan.draggable = true;
pieceSpan.addEventListener('dragstart', handleDragStartQueen);
pieceSpan.addEventListener('dragend', handleDragEndQueen);
}
}
}
else if (r === kingPos.row && c === kingPos.col) {
pieceSpan = document.createElement('span');
pieceSpan.classList.add('piece-emoji');
pieceSpan.textContent = PIECE_EMOJI.BLACK_KING;
pieceSpan.dataset.piece = 'king';
if (currentTurn === 'black' && !gameOver) {
pieceSpan.classList.add('active-piece-animation');
}
}
if (pieceSpan) {
cell.appendChild(pieceSpan);
}
});
}
function animatePieceMove(pieceEmojiText, oldLogicPos, newLogicPos, onAnimationComplete) {
isAnimatingMove = true;
const boardRect = boardElement.getBoundingClientRect();
const oldCellElement = getCellElement(oldLogicPos.row, oldLogicPos.col);
const newCellElement = getCellElement(newLogicPos.row, newLogicPos.col);
if (!oldCellElement || !newCellElement) {
console.error("Animation cells not found:", oldLogicPos, newLogicPos);
isAnimatingMove = false;
onAnimationComplete();
return;
}
const oldCellRect = oldCellElement.getBoundingClientRect();
const newCellRect = newCellElement.getBoundingClientRect();
const ghostPiece = document.createElement('span');
ghostPiece.textContent = pieceEmojiText;
ghostPiece.classList.add('piece-emoji');
if (pieceEmojiText === PIECE_EMOJI.WHITE_QUEEN) ghostPiece.dataset.piece = 'queen';
if (pieceEmojiText === PIECE_EMOJI.BLACK_KING) ghostPiece.dataset.piece = 'king';
ghostPiece.style.position = 'absolute';
ghostPiece.style.left = `${oldCellRect.left - boardRect.left}px`;
ghostPiece.style.top = `${oldCellRect.top - boardRect.top}px`;
ghostPiece.style.width = `${oldCellRect.width}px`;
ghostPiece.style.height = `${oldCellRect.height}px`;
ghostPiece.style.display = 'flex';
ghostPiece.style.alignItems = 'center';
ghostPiece.style.justifyContent = 'center';
ghostPiece.style.zIndex = '1000';
ghostPiece.style.pointerEvents = 'none';
const pieceInOldCell = oldCellElement.querySelector('.piece-emoji');
if (pieceInOldCell) {
pieceInOldCell.style.visibility = 'hidden';
}
boardElement.appendChild(ghostPiece);
ghostPiece.getBoundingClientRect();
const deltaX = newCellRect.left - oldCellRect.left;
const deltaY = newCellRect.top - oldCellRect.top;
ghostPiece.style.transition = `transform ${ANIMATION_DURATION_MS / 1000}s ease-in-out`;
ghostPiece.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
ghostPiece.addEventListener('transitionend', () => {
if (ghostPiece.parentNode) {
ghostPiece.parentNode.removeChild(ghostPiece);
}
isAnimatingMove = false;
onAnimationComplete();
}, { once: true });
}
function handleCellClick(event) {
if (isAnimatingMove || gameOver || currentTurn !== 'white') return;
const clickedRow = parseInt(event.currentTarget.dataset.row);
const clickedCol = parseInt(event.currentTarget.dataset.col);
const move = queenPossibleMoves.find(m => m.row === clickedRow && m.col === clickedCol);
if (move) {
makeQueenMove(clickedRow, clickedCol);
}
}
function makeQueenMove(newRow, newCol) {
if (isAnimatingMove || gameOver || currentTurn !== 'white') return;
const oldPos = { ...queenPos };
const newPos = { row: newRow, col: newCol };
animatePieceMove(PIECE_EMOJI.WHITE_QUEEN, oldPos, newPos, () => {
queenPos = newPos;
if (navigator.vibrate) navigator.vibrate(20);
checkWinConditions('white');
if (!gameOver) {
currentTurn = 'black';
updateMessage();
drawPiecesAndHighlights();
setTimeout(computerMove, 700);
} else if (!skullAnimationOverlay.classList.contains('fading-to-black') && skullAnimationOverlay.style.display !== 'block' ) {
drawPiecesAndHighlights();
}
});
}
function getValidMovesForPiece(piecePos, pieceType, currentKingPos = kingPos) {
const moves = [];
const { row, col } = piecePos;
if (pieceType === 'queen') {
const directions = [
{ dr: 0, dc: 1 }, { dr: 0, dc: -1 }, { dr: 1, dc: 0 }, { dr: -1, dc: 0 },
{ dr: 1, dc: 1 }, { dr: 1, dc: -1 }, { dr: -1, dc: 1 }, { dr: -1, dc: -1 }
];
for (const dir of directions) {
for (let i = 1; i < Math.max(BOARD_SIZE.rows, BOARD_SIZE.cols); i++) {
const newR = row + dir.dr * i; const newC = col + dir.dc * i;
if (!isValidBoardPos(newR, newC)) break;
if (newR === currentKingPos.row && newC === currentKingPos.col) {
moves.push({ row: newR, col: newC }); break;
}
moves.push({ row: newR, col: newC });
}
}
} else if (pieceType === 'king') {
const KingOffsets = [
{ dr: -1, dc: -1 }, { dr: -1, dc: 0 }, { dr: -1, dc: 1 }, { dr: 0, dc: -1 },
{ dr: 0, dc: 1 }, { dr: 1, dc: -1 }, { dr: 1, dc: 0 }, { dr: 1, dc: 1 }
];
for (const offset of KingOffsets) {
const newR = row + offset.dr; const newC = col + offset.dc;
if (isValidBoardPos(newR, newC)) {
if (newR === queenPos.row && newC === queenPos.col) {
moves.push({ row: newR, col: newC });
}
else if (!isSquareAttackedByQueen({row: newR, col: newC}, queenPos, {row:newR, col:newC})) {
moves.push({ row: newR, col: newC });
}
}
}
}
return moves;
}
function isValidBoardPos(row, col) {
return row >= 0 && row < BOARD_SIZE.rows && col >= 0 && col < BOARD_SIZE.cols;
}
function isSquareAttackedByQueen(targetPos, currentQueenPos, currentKingPosForLoS) {
const { row: qR, col: qC } = currentQueenPos;
const { row: tR, col: tC } = targetPos;
if (tR === qR && tC === qC) return false;
if (tR === qR || tC === qC || Math.abs(tR - qR) === Math.abs(tC - qC)) {
const dr = Math.sign(tR - qR);
const dc = Math.sign(tC - qC);
let r = qR + dr;
let c = qC + dc;
while ((r !== tR || c !== tC) && isValidBoardPos(r,c)) {
if (r === currentKingPosForLoS.row && c === currentKingPosForLoS.col &&
!(r === tR && c === tC) ) {
return false;
}
r += dr;
c += dc;
}
return (r === tR && c === tC);
}
return false;
}
function computerMove() {
if (isAnimatingMove || gameOver || currentTurn !== 'black') return;
const kingValidMoves = getValidMovesForPiece(kingPos, 'king');
let chosenMove = null;
const oldKingPos = { ...kingPos };
const captureQueenMove = kingValidMoves.find(m => m.row === queenPos.row && m.col === queenPos.col);
if (captureQueenMove) {
chosenMove = captureQueenMove;
} else {
const nonSkullMoves = kingValidMoves.filter(m => !(m.row === SKULL_TARGET.row && m.col === SKULL_TARGET.col));
if (nonSkullMoves.length > 0) {
nonSkullMoves.sort((a, b) => {
const distA = Math.hypot(a.row - queenPos.row, a.col - queenPos.col);
const distB = Math.hypot(b.row - queenPos.row, b.col - queenPos.col);
return distB - distA;
});
chosenMove = nonSkullMoves[0];
} else if (kingValidMoves.length > 0) {
chosenMove = kingValidMoves[0];
}
}
if (chosenMove) {
const newKingPos = chosenMove;
animatePieceMove(PIECE_EMOJI.BLACK_KING, oldKingPos, newKingPos, () => {
kingPos = newKingPos;
checkWinConditions('black');
if (!gameOver) {
currentTurn = 'white';
updateMessage();
drawPiecesAndHighlights();
} else if (!skullAnimationOverlay.classList.contains('fading-to-black') && skullAnimationOverlay.style.display !== 'block' ) {
drawPiecesAndHighlights();
}
});
} else {
if (isSquareAttackedByQueen(kingPos, queenPos, kingPos)) {
endGame(`${PIECE_EMOJI.WHITE_QUEEN} wins! Checkmate.`);
} else {
endGame(`Stalemate! ${PIECE_EMOJI.BLACK_KING} has no moves. White wins!`);
}
drawPiecesAndHighlights();
}
}
function checkWinConditions(whoMoved) {
if (gameOver && !(whoMoved === 'black' && kingPos.row === SKULL_TARGET.row && kingPos.col === SKULL_TARGET.col)) {
return;
}
if (whoMoved === 'white') {
if (queenPos.row === kingPos.row && queenPos.col === kingPos.col) {
endGame(`${PIECE_EMOJI.WHITE_QUEEN} wins! Captured ${PIECE_EMOJI.BLACK_KING} at ${coordsToAlg(kingPos)}.`);
}
} else if (whoMoved === 'black') {
if (kingPos.row === queenPos.row && kingPos.col === queenPos.col) {
endGame(`${PIECE_EMOJI.BLACK_KING} wins! Captured ${PIECE_EMOJI.WHITE_QUEEN} at ${coordsToAlg(queenPos)}!`);
} else if (kingPos.row === SKULL_TARGET.row && kingPos.col === SKULL_TARGET.col) {
const message = `${PIECE_EMOJI.WHITE_QUEEN} wins! ${PIECE_EMOJI.BLACK_KING} forced to ${PIECE_EMOJI.SKULL} at ${coordsToAlg(SKULL_TARGET)}!`;
playKingHitsSkullAnimation(message, () => {
drawPiecesAndHighlights();
});
return;
}
}
if (!gameOver && currentTurn === 'white') {
const kingMoves = getValidMovesForPiece(kingPos, 'king');
if (kingMoves.length === 0) {
if (isSquareAttackedByQueen(kingPos, queenPos, kingPos)) {
endGame(`${PIECE_EMOJI.WHITE_QUEEN} wins! Checkmate.`);
} else {
endGame(`Stalemate! ${PIECE_EMOJI.BLACK_KING} has no moves. White wins!`);
}
}
}
}
function playKingHitsSkullAnimation(endGameMessage, onAnimationEndCallback) {
gameOver = true;
isAnimatingMove = true;
messageElement.textContent = "☠️ The King meets his fate... ☠️";
subtleMoveMessageElement.textContent = '';
playAgainButton.style.display = 'none';
const kingCellElement = getCellElement(kingPos.row, kingPos.col);
const kingEmojiOnBoard = kingCellElement ? kingCellElement.querySelector('.piece-emoji[data-piece="king"]') : null;
if (!kingEmojiOnBoard || !skullAnimationOverlay || !animatedSkullElement) {
console.error("Skull animation elements missing.");
endGame(endGameMessage);
if (onAnimationEndCallback) onAnimationEndCallback();
isAnimatingMove = false;
return;
}
kingEmojiOnBoard.classList.add('king-on-skull-fading');
const kingRect = kingEmojiOnBoard.getBoundingClientRect();
const kingComputedStyle = window.getComputedStyle(kingEmojiOnBoard);
const initialSkullFontSize = kingComputedStyle.fontSize;
animatedSkullElement.style.left = `${kingRect.left}px`;
animatedSkullElement.style.top = `${kingRect.top}px`;
animatedSkullElement.style.width = `${kingRect.width}px`;
animatedSkullElement.style.height = `${kingRect.height}px`;
animatedSkullElement.style.fontSize = initialSkullFontSize;
animatedSkullElement.style.transform = 'translate(0,0) scale(1)';
animatedSkullElement.style.opacity = '0';
skullAnimationOverlay.style.backgroundColor = 'rgba(0,0,0,0)';
skullAnimationOverlay.style.display = 'block';
animatedSkullElement.getBoundingClientRect();
animatedSkullElement.classList.add('trigger-skull-win-animation');
const growAnimationTotalTime = 2100;
setTimeout(() => {
skullAnimationOverlay.classList.add('fading-to-black');
setTimeout(() => {
skullAnimationOverlay.classList.remove('fading-to-black');
skullAnimationOverlay.classList.add('fading-out-from-black');
setTimeout(() => {
skullAnimationOverlay.style.display = 'none';
skullAnimationOverlay.classList.remove('fading-out-from-black');
animatedSkullElement.classList.remove('trigger-skull-win-animation');
animatedSkullElement.style.opacity = '0';
if (kingEmojiOnBoard) kingEmojiOnBoard.classList.remove('king-on-skull-fading');
endGame(endGameMessage);
if (onAnimationEndCallback) onAnimationEndCallback();
isAnimatingMove = false;
playAgainButton.style.display = 'inline-flex';
}, 1000);
}, 1000);
}, growAnimationTotalTime);
}
function updateMessage() {
if (gameOver) return;
if (currentTurn === 'white') messageElement.textContent = `${PIECE_EMOJI.WHITE_QUEEN} to move`;
else messageElement.textContent = `${PIECE_EMOJI.BLACK_KING} thinking...`;
}
function endGame(msg) {
gameOver = true;
messageElement.textContent = msg;
subtleMoveMessageElement.textContent = '';
if (!skullAnimationOverlay.classList.contains('fading-to-black') && skullAnimationOverlay.style.display !== 'block' ) {
playAgainButton.style.display = 'inline-flex';
}
queenPossibleMoves = [];
}
function coordsToAlg(pos) {
const colChar = String.fromCharCode(97 + pos.col);
const rowNum = BOARD_SIZE.rows - pos.row;
return `${colChar}${rowNum}`;
}
function resetGame() {
isAnimatingMove = false;
kingPos = { ...INITIAL_KING_POS };
queenPos = { ...INITIAL_QUEEN_POS };
currentTurn = 'white';
gameOver = false;
playAgainButton.style.display = 'none';
explanationText.style.display = 'none'; // Hide help text
explanationBtn.classList.remove('active');
explanationBtn.setAttribute('aria-expanded', 'false');
skullAnimationOverlay.style.display = 'none';
skullAnimationOverlay.classList.remove('fading-to-black', 'fading-out-from-black');
animatedSkullElement.classList.remove('trigger-skull-win-animation');
animatedSkullElement.style.opacity = '0';
updateMessage();
subtleMoveMessageElement.textContent = '';
drawPiecesAndHighlights();
}
// --- Drag and Drop Handlers ---
function handleDragStartQueen(event) {
if (isAnimatingMove || currentTurn !== 'white' || gameOver) {
event.preventDefault(); return;
}
draggedPieceElement = event.target;
originalQueenPos = { ...queenPos };
event.dataTransfer.effectAllowed = 'move';
setTimeout(() => { if (draggedPieceElement) draggedPieceElement.classList.add('dragging'); }, 0);
}
function handleDragOver(event) {
if (isAnimatingMove) return;
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}
function handleDragEnterCell(event) { /* No visual feedback */ }
function handleDragLeaveCell(event) { /* No visual feedback */ }
function handleDrop(event) {
event.preventDefault();
if (isAnimatingMove || currentTurn !== 'white' || gameOver || !originalQueenPos) return;
const cell = event.currentTarget;
const r = parseInt(cell.dataset.row);
const c = parseInt(cell.dataset.col);
if (queenPossibleMoves.length === 0 && currentTurn === 'white' && !gameOver) {
queenPossibleMoves = getValidMovesForPiece(queenPos, 'queen');
}
const move = queenPossibleMoves.find(m => m.row === r && m.col === c);
if (move) {
makeQueenMove(r, c);
}
}
function handleDragEndQueen(event) {
if (draggedPieceElement) {
draggedPieceElement.classList.remove('dragging');
}
draggedPieceElement = null;
originalQueenPos = null;
if (!isAnimatingMove) {
drawPiecesAndHighlights();
}
}
playAgainButton.addEventListener('click', resetGame);
// Original simple toggle for explanation text
explanationBtn.addEventListener('click', () => {
const currentlyVisible = explanationText.style.display === 'block';
explanationText.style.display = currentlyVisible ? 'none' : 'block';
explanationBtn.classList.toggle('active', !currentlyVisible);
explanationBtn.setAttribute('aria-expanded', String(!currentlyVisible));
});
createBoard();
resetGame();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment