<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>16 Queens Puzzle</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>16 Queens Puzzle</h1>
<div id="instructions">
<h2>How to Play:</h2>
<p>The goal is to place 16 queens on the 16x16 chessboard such that no two queens threaten each other.</p>
<ul>
<li>No two queens can be in the same row.</li>
<li>No two queens can be in the same column.</li>
<li>No two queens can be on the same diagonal.</li>
</ul>
<p>Click on an empty square to place a queen. Click on a square with a queen to remove it.</p>
<p>Use the "Show Hints" button to highlight squares currently under attack by existing queens.</p>
</div>
<div class="controls">
<button id="reset-button">Reset Game</button>
<button id="hint-button">Show Hints</button>
<p id="message">Place 16 queens so that none attack each other.</p>
<p>Queens placed: <span id="queen-count">0</span> / 16</p>
</div>
<div id="chessboard">
<!-- Squares will be generated by JavaScript -->
</div>
<script src="script.js"></script>
</body>
</html>
/* --- Import Google Fonts --- */
@import url('https://fonts.googleapis.com/css2?family=Merriweather:wght@400;700&family=Lato:wght@400;700&display=swap');
/* --- Global Styles & Variables --- */
:root {
--font-primary: 'Lato', sans-serif;
--font-heading: 'Merriweather', serif;
--color-bg: #f4f0e8; /* Creamy background */
--color-dark-wood: #8B4513; /* Saddle Brown */
--color-light-wood: #DEB887; /* Burly Wood */
--color-dark-wood-gradient: linear-gradient(145deg, #a0522d, #8b4513);
--color-light-wood-gradient: linear-gradient(145deg, #f5deb3, #deb887);
--color-queen: #111;
--color-hint: rgba(255, 50, 50, 0.55); /* Brighter, slightly more opaque red */
--color-win: #28a745; /* Success Green */
--color-button-reset: #dc3545; /* Danger Red */
--color-button-reset-hover: #c82333;
--color-button-hint: #17a2b8; /* Info Blue */
--color-button-hint-hover: #138496;
--shadow-light: 0 2px 5px rgba(0, 0, 0, 0.1);
--shadow-medium: 0 5px 15px rgba(0, 0, 0, 0.2);
--shadow-heavy: 0 10px 25px rgba(0, 0, 0, 0.3);
}
body {
font-family: var(--font-primary);
display: flex;
flex-direction: column;
align-items: center;
background-color: var(--color-bg);
background-image: linear-gradient(to bottom, #ffffff, var(--color-bg) 150px); /* Subtle gradient */
margin: 0;
padding: 30px 15px 60px 15px;
color: #333;
line-height: 1.6;
}
h1, h2 {
font-family: var(--font-heading);
color: #4a2c1a; /* Darker brown for headings */
text-align: center;
margin-bottom: 0.75em;
}
h1 {
font-size: 2.8em;
text-shadow: 1px 1px 2px rgba(0,0,0,0.1);
}
h2 {
font-size: 1.6em;
border-bottom: 1px solid #d4c8b4;
padding-bottom: 5px;
}
/* --- Instructions Area --- */
#instructions {
background-color: #fffaf0; /* Floral White */
padding: 20px 25px;
border-radius: 12px;
margin-bottom: 30px;
max-width: 800px;
box-shadow: var(--shadow-medium);
border: 1px solid #eaddc5;
}
#instructions ul {
list-style-type: disc; /* Use standard bullets */
list-style-position: outside; /* Proper indentation */
padding-left: 25px; /* Indent list items */
margin-top: 10px;
}
#instructions li {
margin-bottom: 8px;
}
/* --- Controls Area --- */
.controls {
margin-bottom: 30px;
text-align: center;
width: 100%;
max-width: 800px;
}
.controls p {
margin: 12px 0;
font-size: 1.1em;
}
.controls button {
padding: 12px 25px;
font-size: 1.1em;
font-family: var(--font-primary);
font-weight: bold;
cursor: pointer;
color: white;
border: none;
border-radius: 8px;
margin: 5px 8px 15px 8px;
transition: all 0.25s ease-out;
box-shadow: var(--shadow-light);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.controls button:hover {
transform: translateY(-3px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
}
.controls button:active {
transform: translateY(-1px);
box-shadow: var(--shadow-light);
}
#reset-button {
background-color: var(--color-button-reset);
}
#reset-button:hover {
background-color: var(--color-button-reset-hover);
}
#hint-button {
background-color: var(--color-button-hint);
}
#hint-button:hover {
background-color: var(--color-button-hint-hover);
}
#message {
font-style: italic;
color: #555;
min-height: 25px;
font-size: 1.1em;
font-weight: bold;
transition: color 0.3s ease;
}
#queen-count {
font-weight: bold;
background-color: #e9ecef;
padding: 2px 8px;
border-radius: 4px;
}
/* --- Chessboard Styling --- */
#chessboard {
/* Keep dimensions from previous step (e.g., 640px) */
width: 640px;
height: 640px;
display: grid;
grid-template-columns: repeat(16, 1fr);
grid-template-rows: repeat(16, 1fr);
border: 10px solid #603813; /* Thicker, darker wood frame */
box-shadow: var(--shadow-heavy), 0 0 0 1px #9d7a5c; /* Heavy shadow + subtle edge */
border-radius: 6px; /* Slightly rounded board corners */
background-color: var(--color-dark-wood); /* Fallback/gap color */
}
/* --- Square Styling --- */
.square {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.8em; /* Keep queen size reasonable */
cursor: pointer;
user-select: none;
transition: transform 0.15s ease-out, box-shadow 0.15s ease-out, background-color 0.2s ease;
position: relative; /* Needed for pseudo-elements and z-index */
box-sizing: border-box;
/* Remove border, use gradients */
}
.square.light {
background: var(--color-light-wood-gradient);
}
.square.dark {
background: var(--color-dark-wood-gradient);
}
/* --- Square Hover Effect --- */
.square:not(.queen):hover {
transform: scale(1.08);
z-index: 10; /* Bring hovered square to front */
box-shadow: var(--shadow-medium);
filter: brightness(1.1); /* Slightly brighten on hover */
}
/* --- Queen Styling --- */
.square.queen::before {
content: '♛';
color: var(--color-queen);
text-shadow: 1px 1px 0px #fff, /* White highlight top-left */
-1px -1px 0px rgba(0,0,0,0.2); /* Dark shadow bottom-right */
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); /* Bounce effect */
/* Animation on placement */
animation: placeQueen 0.3s ease-out;
}
/* Animation for queen placement */
@keyframes placeQueen {
0% { transform: scale(0.5); opacity: 0; }
80% { transform: scale(1.1); opacity: 1; }
100% { transform: scale(1); opacity: 1; }
}
/* Slight scale effect when hovering over a queen */
.square.queen:hover::before {
transform: scale(1.1);
}
/* --- Hint Styling --- */
.square.hint-attacked {
/* Use a pseudo-element for the overlay to avoid affecting gradients */
position: relative; /* Needed for pseudo-element positioning */
}
.square.hint-attacked::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--color-hint);
border-radius: 3px; /* Match square rounding if any */
pointer-events: none; /* Allow clicks to pass through */
animation: pulseHint 1.5s infinite ease-in-out;
z-index: 5; /* Above square background, below queen */
}
/* Animation for hint pulse */
@keyframes pulseHint {
0% { opacity: 0.5; }
50% { opacity: 0.8; }
100% { opacity: 0.5; }
}
/* --- Win State Styling --- */
.win #message {
color: var(--color-win);
font-weight: bold;
font-size: 1.4em; /* Make win message stand out */
text-shadow: 1px 1px 1px rgba(0,0,0,0.1);
animation: fadeInWin 0.5s ease-in;
}
@keyframes fadeInWin {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.win #hint-button {
opacity: 0.5;
cursor: not-allowed;
transform: none; /* Disable hover transform */
box-shadow: var(--shadow-light); /* Reset shadow */
}
/* Optional: Add subtle shake for invalid move feedback (requires JS change to add/remove class) */
@keyframes shake {
10%, 90% { transform: translate3d(-1px, 0, 0); }
20%, 80% { transform: translate3d(2px, 0, 0); }
30%, 50%, 70% { transform: translate3d(-3px, 0, 0); }
40%, 60% { transform: translate3d(3px, 0, 0); }
}
.invalid-shake {
animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both;
}
document.addEventListener('DOMContentLoaded', () => {
const boardSize = 16; // <-- Change board size to 16
const chessboard = document.getElementById('chessboard');
const messageElement = document.getElementById('message');
const queenCountElement = document.getElementById('queen-count');
const resetButton = document.getElementById('reset-button');
const hintButton = document.getElementById('hint-button');
const bodyElement = document.body;
let boardState = Array(boardSize).fill(null).map(() => Array(boardSize).fill(0)); // 0: empty, 1: queen
let queenCount = 0;
let gameOver = false;
let hintActive = false; // To manage hint state
// --- Board Generation ---
function createBoard() {
chessboard.innerHTML = ''; // Clear previous board
for (let row = 0; row < boardSize; row++) {
for (let col = 0; col < boardSize; col++) {
const square = document.createElement('div');
square.classList.add('square');
square.classList.add(((row + col) % 2 === 0) ? 'light' : 'dark');
square.dataset.row = row;
square.dataset.col = col;
square.addEventListener('click', handleSquareClick);
chessboard.appendChild(square);
}
}
}
// --- Game Logic ---
// Checks if placing a queen at (row, col) is SAFE
function isSafe(row, col) {
// Check if the square itself already has a queen (needed for hint logic)
if (boardState[row][col] === 1) return true; // It's safe relative to itself
// Check row and column for other queens
for (let i = 0; i < boardSize; i++) {
if (boardState[row][i] === 1) return false; // Check row
if (boardState[i][col] === 1) return false; // Check column
}
// Check diagonals
for (let i = 1; i < boardSize; i++) {
// Top-left
if (row - i >= 0 && col - i >= 0 && boardState[row - i][col - i] === 1) return false;
// Top-right
if (row - i >= 0 && col + i < boardSize && boardState[row - i][col + i] === 1) return false;
// Bottom-left
if (row + i < boardSize && col - i >= 0 && boardState[row + i][col - i] === 1) return false;
// Bottom-right
if (row + i < boardSize && col + i < boardSize && boardState[row + i][col + i] === 1) return false;
}
return true;
}
// Checks if a specific square (row, col) is attacked by ANY existing queen
function isAttacked(row, col) {
// Check row and column
for (let i = 0; i < boardSize; i++) {
if (i !== col && boardState[row][i] === 1) return true; // Check row (exclude self implicitly as we only check empty squares for hints)
if (i !== row && boardState[i][col] === 1) return true; // Check column
}
// Check diagonals
for (let i = 1; i < boardSize; i++) {
if (row - i >= 0 && col - i >= 0 && boardState[row - i][col - i] === 1) return true;
if (row - i >= 0 && col + i < boardSize && boardState[row - i][col + i] === 1) return true;
if (row + i < boardSize && col - i >= 0 && boardState[row + i][col - i] === 1) return true;
if (row + i < boardSize && col + i < boardSize && boardState[row + i][col + i] === 1) return true;
}
return false;
}
function handleSquareClick(event) {
if (gameOver) return; // Don't allow changes after winning
const square = event.target;
const row = parseInt(square.dataset.row);
const col = parseInt(square.dataset.col);
clearHints(); // Clear hints on any valid click
resetMessage();
if (boardState[row][col] === 1) {
// Remove queen
boardState[row][col] = 0;
square.classList.remove('queen');
queenCount--;
messageElement.textContent = "Queen removed.";
} else {
// Try to place queen
if (isSafe(row, col)) {
boardState[row][col] = 1;
square.classList.add('queen');
queenCount++;
messageElement.textContent = "Queen placed.";
if (queenCount === boardSize) {
checkWinCondition();
}
} else {
messageElement.textContent = "Invalid move! A queen here would be attacked.";
// Briefly flash the clicked square or attacking queens (optional enhancement)
}
}
updateQueenCount();
}
function updateQueenCount() {
queenCountElement.textContent = `${queenCount} / ${boardSize}`;
}
function checkWinCondition() {
// Since we prevent invalid moves by checking isSafe before placement,
// reaching boardSize queens means success.
if (queenCount === boardSize) {
messageElement.textContent = `Congratulations! You solved the ${boardSize} Queens puzzle!`;
bodyElement.classList.add('win');
gameOver = true;
hintButton.disabled = true; // Disable hint button on win
}
}
// --- Hint System ---
function showHints() {
if (gameOver || hintActive) return; // Don't show hints if game over or already showing
clearHints(); // Clear previous hints first
hintActive = true;
let hintsShown = 0;
messageElement.textContent = "Highlighting attacked empty squares...";
const squares = chessboard.querySelectorAll('.square');
squares.forEach(square => {
const r = parseInt(square.dataset.row);
const c = parseInt(square.dataset.col);
// Only check empty squares
if (boardState[r][c] === 0) {
if (isAttacked(r, c)) {
square.classList.add('hint-attacked');
hintsShown++;
}
}
});
if (hintsShown === 0 && queenCount > 0) {
messageElement.textContent = "No empty squares are currently under attack.";
} else if (queenCount === 0) {
messageElement.textContent = "Place a queen first to see attacked squares.";
}
// Automatically clear hints after a delay
setTimeout(clearHints, 2500); // Clear hints after 2.5 seconds
}
function clearHints() {
if (!hintActive) return;
const hintedSquares = chessboard.querySelectorAll('.square.hint-attacked');
hintedSquares.forEach(sq => sq.classList.remove('hint-attacked'));
resetMessage(); // Reset message after clearing hints
hintActive = false;
}
// --- Reset & Utility ---
function resetMessage() {
if (!gameOver && !hintActive) { // Don't reset win message or hint message immediately
messageElement.textContent = `Place ${boardSize} queens so that none attack each other.`;
}
}
function resetGame() {
boardState = Array(boardSize).fill(null).map(() => Array(boardSize).fill(0));
queenCount = 0;
gameOver = false;
clearHints(); // Ensure hints are cleared
bodyElement.classList.remove('win');
hintButton.disabled = false; // Re-enable hint button
updateQueenCount();
resetMessage(); // Set the default message
// Clear visual representation
const squares = chessboard.querySelectorAll('.square');
squares.forEach(square => {
square.classList.remove('queen');
square.classList.remove('hint-attacked'); // Ensure hints are visually cleared
});
}
// --- Initialization ---
createBoard();
resetButton.addEventListener('click', resetGame);
hintButton.addEventListener('click', showHints);
updateQueenCount(); // Initial count
resetMessage(); // Initial message
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.