Skip to content

Instantly share code, notes, and snippets.

@shricodev
Created May 25, 2025 13:15
Show Gist options
  • Save shricodev/31969dd257e12f0791ab196af7676508 to your computer and use it in GitHub Desktop.
Save shricodev/31969dd257e12f0791ab196af7676508 to your computer and use it in GitHub Desktop.
Blog - Chess (Claude Opus 4)
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Modern Chess Game</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
color: #333;
}
.game-container {
display: flex;
gap: 30px;
padding: 20px;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.board-section {
display: flex;
flex-direction: column;
align-items: center;
}
.timer {
width: 100%;
padding: 15px;
margin-bottom: 15px;
background: #f0f0f0;
border-radius: 10px;
text-align: center;
font-size: 24px;
font-weight: bold;
transition: all 0.3s ease;
}
.timer.active {
background: #4caf50;
color: white;
transform: scale(1.05);
}
.timer.warning {
background: #ff6b6b;
color: white;
}
.board {
display: grid;
grid-template-columns: repeat(8, 75px);
grid-template-rows: repeat(8, 75px);
border: 3px solid #333;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.square {
width: 75px;
height: 75px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
position: relative;
transition: all 0.2s ease;
}
.square.light {
background: #f0d9b5;
}
.square.dark {
background: #b58863;
}
.square.selected {
background: #7fc97f !important;
box-shadow: inset 0 0 0 3px #4caf50;
}
.square.highlight {
background: #fff3b2 !important;
}
.square.possible-move::before {
content: "";
position: absolute;
width: 30%;
height: 30%;
background: rgba(0, 0, 0, 0.2);
border-radius: 50%;
}
.square.possible-capture::before {
content: "";
position: absolute;
width: 100%;
height: 100%;
border: 3px solid rgba(255, 0, 0, 0.5);
border-radius: 50%;
}
.piece {
font-size: 50px;
cursor: grab;
user-select: none;
transition: transform 0.1s ease;
}
.piece:hover {
transform: scale(1.1);
}
.piece:active {
cursor: grabbing;
transform: scale(1.2);
}
.info-section {
display: flex;
flex-direction: column;
min-width: 280px;
}
.status {
padding: 20px;
background: #f8f8f8;
border-radius: 10px;
margin-bottom: 20px;
text-align: center;
}
.status h2 {
margin-bottom: 10px;
color: #333;
}
.turn-indicator {
font-size: 18px;
font-weight: bold;
padding: 10px 20px;
background: #4caf50;
color: white;
border-radius: 25px;
display: inline-block;
}
.moves-container {
flex: 1;
background: #f8f8f8;
border-radius: 10px;
padding: 20px;
overflow-y: auto;
max-height: 400px;
}
.moves-container h3 {
margin-bottom: 15px;
color: #333;
}
.move-list {
font-family: "Courier New", monospace;
font-size: 14px;
line-height: 1.6;
}
.move-pair {
display: grid;
grid-template-columns: 30px 1fr 1fr;
gap: 10px;
margin-bottom: 5px;
padding: 5px;
border-radius: 5px;
transition: background 0.2s;
}
.move-pair:hover {
background: #e0e0e0;
}
.move-number {
font-weight: bold;
color: #666;
}
.button {
padding: 12px 30px;
background: #4caf50;
color: white;
border: none;
border-radius: 25px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 20px;
}
.button:hover {
background: #45a049;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
.game-over-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.game-over-modal.show {
opacity: 1;
visibility: visible;
}
.modal-content {
background: white;
padding: 40px;
border-radius: 20px;
text-align: center;
transform: scale(0.8);
transition: transform 0.3s ease;
}
.game-over-modal.show .modal-content {
transform: scale(1);
}
.modal-content h2 {
font-size: 36px;
margin-bottom: 20px;
color: #333;
}
.modal-content p {
font-size: 20px;
margin-bottom: 30px;
color: #666;
}
@media (max-width: 768px) {
.game-container {
flex-direction: column;
}
.board {
grid-template-columns: repeat(8, 45px);
grid-template-rows: repeat(8, 45px);
}
.square {
width: 45px;
height: 45px;
}
.piece {
font-size: 30px;
}
}
</style>
</head>
<body>
<div class="game-container">
<div class="board-section">
<div class="timer" id="blackTimer">⏱ Black: 5:00</div>
<div class="board" id="board"></div>
<div class="timer" id="whiteTimer">⏱ White: 5:00</div>
</div>
<div class="info-section">
<div class="status">
<h2>Chess Game</h2>
<div class="turn-indicator" id="turnIndicator">White's Turn</div>
<div
id="checkStatus"
style="margin-top: 10px; color: #ff6b6b; font-weight: bold"
></div>
</div>
<div class="moves-container">
<h3>Move History</h3>
<div class="move-list" id="moveList"></div>
</div>
<button class="button" onclick="resetGame()">New Game</button>
</div>
</div>
<div class="game-over-modal" id="gameOverModal">
<div class="modal-content">
<h2 id="gameOverTitle">Game Over</h2>
<p id="gameOverMessage"></p>
<button class="button" onclick="resetGame()">Play Again</button>
</div>
</div>
<script>
// Chess piece Unicode symbols
const PIECES = {
K: "♔",
Q: "♕",
R: "♖",
B: "♗",
N: "♘",
P: "♙",
k: "♚",
q: "♛",
r: "♜",
b: "♝",
n: "♞",
p: "♟",
};
// Initial board setup
const INITIAL_BOARD = [
["r", "n", "b", "q", "k", "b", "n", "r"],
["p", "p", "p", "p", "p", "p", "p", "p"],
[null, null, null, null, null, null, null, null],
[null, null, null, null, null, null, null, null],
[null, null, null, null, null, null, null, null],
[null, null, null, null, null, null, null, null],
["P", "P", "P", "P", "P", "P", "P", "P"],
["R", "N", "B", "Q", "K", "B", "N", "R"],
];
// Game state
let board = [];
let currentPlayer = "white";
let selectedSquare = null;
let moveHistory = [];
let lastMove = null;
let whiteTime = 300; // 5 minutes in seconds
let blackTime = 300;
let timerInterval = null;
let gameOver = false;
// Initialize the game
function initializeGame() {
board = INITIAL_BOARD.map((row) => [...row]);
renderBoard();
updateStatus();
startTimer();
}
// Render the chess board
function renderBoard() {
const boardElement = document.getElementById("board");
boardElement.innerHTML = "";
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
const square = document.createElement("div");
square.className =
"square " + ((row + col) % 2 === 0 ? "light" : "dark");
square.dataset.row = row;
square.dataset.col = col;
square.onclick = () => handleSquareClick(row, col);
// Highlight last move
if (
lastMove &&
((lastMove.from.row === row && lastMove.from.col === col) ||
(lastMove.to.row === row && lastMove.to.col === col))
) {
square.classList.add("highlight");
}
const piece = board[row][col];
if (piece) {
const pieceElement = document.createElement("div");
pieceElement.className = "piece";
pieceElement.textContent = PIECES[piece];
square.appendChild(pieceElement);
}
boardElement.appendChild(square);
}
}
}
// Handle square clicks
function handleSquareClick(row, col) {
if (gameOver) return;
if (selectedSquare) {
// Try to make a move
if (isValidMove(selectedSquare.row, selectedSquare.col, row, col)) {
makeMove(selectedSquare.row, selectedSquare.col, row, col);
}
clearSelection();
} else {
// Select a piece
const piece = board[row][col];
if (piece && isPlayerPiece(piece)) {
selectSquare(row, col);
}
}
}
// Select a square
function selectSquare(row, col) {
selectedSquare = { row, col };
highlightSelectedSquare();
showPossibleMoves(row, col);
}
// Clear selection
function clearSelection() {
selectedSquare = null;
document.querySelectorAll(".square").forEach((square) => {
square.classList.remove(
"selected",
"possible-move",
"possible-capture",
);
});
}
// Highlight selected square
function highlightSelectedSquare() {
const squares = document.querySelectorAll(".square");
squares.forEach((square) => {
if (
parseInt(square.dataset.row) === selectedSquare.row &&
parseInt(square.dataset.col) === selectedSquare.col
) {
square.classList.add("selected");
}
});
}
// Show possible moves
function showPossibleMoves(row, col) {
for (let r = 0; r < 8; r++) {
for (let c = 0; c < 8; c++) {
if (isValidMove(row, col, r, c)) {
const square = document.querySelector(
`[data-row="${r}"][data-col="${c}"]`,
);
if (board[r][c]) {
square.classList.add("possible-capture");
} else {
square.classList.add("possible-move");
}
}
}
}
}
// Check if a piece belongs to the current player
function isPlayerPiece(piece) {
return (
(currentPlayer === "white" && piece === piece.toUpperCase()) ||
(currentPlayer === "black" && piece === piece.toLowerCase())
);
}
// Check if a move is valid
function isValidMove(fromRow, fromCol, toRow, toCol) {
const piece = board[fromRow][fromCol];
if (!piece || !isPlayerPiece(piece)) return false;
if (fromRow === toRow && fromCol === toCol) return false;
const targetPiece = board[toRow][toCol];
if (targetPiece && isPlayerPiece(targetPiece)) return false;
// Check piece-specific moves
switch (piece.toLowerCase()) {
case "p":
return isValidPawnMove(fromRow, fromCol, toRow, toCol, piece);
case "n":
return isValidKnightMove(fromRow, fromCol, toRow, toCol);
case "b":
return isValidBishopMove(fromRow, fromCol, toRow, toCol);
case "r":
return isValidRookMove(fromRow, fromCol, toRow, toCol);
case "q":
return isValidQueenMove(fromRow, fromCol, toRow, toCol);
case "k":
return isValidKingMove(fromRow, fromCol, toRow, toCol);
}
return false;
}
// Pawn move validation
function isValidPawnMove(fromRow, fromCol, toRow, toCol, piece) {
const direction = piece === piece.toUpperCase() ? -1 : 1;
const startRow = piece === piece.toUpperCase() ? 6 : 1;
// Forward move
if (fromCol === toCol && !board[toRow][toCol]) {
if (toRow === fromRow + direction) return true;
if (
fromRow === startRow &&
toRow === fromRow + 2 * direction &&
!board[fromRow + direction][fromCol]
)
return true;
}
// Capture
if (
Math.abs(fromCol - toCol) === 1 &&
toRow === fromRow + direction &&
board[toRow][toCol]
) {
return true;
}
return false;
}
// Knight move validation
function isValidKnightMove(fromRow, fromCol, toRow, toCol) {
const rowDiff = Math.abs(toRow - fromRow);
const colDiff = Math.abs(toCol - fromCol);
return (
(rowDiff === 2 && colDiff === 1) || (rowDiff === 1 && colDiff === 2)
);
}
// Bishop move validation
function isValidBishopMove(fromRow, fromCol, toRow, toCol) {
if (Math.abs(toRow - fromRow) !== Math.abs(toCol - fromCol))
return false;
return isDiagonalClear(fromRow, fromCol, toRow, toCol);
}
// Rook move validation
function isValidRookMove(fromRow, fromCol, toRow, toCol) {
if (fromRow !== toRow && fromCol !== toCol) return false;
return isStraightClear(fromRow, fromCol, toRow, toCol);
}
// Queen move validation
function isValidQueenMove(fromRow, fromCol, toRow, toCol) {
return (
isValidBishopMove(fromRow, fromCol, toRow, toCol) ||
isValidRookMove(fromRow, fromCol, toRow, toCol)
);
}
// King move validation
function isValidKingMove(fromRow, fromCol, toRow, toCol) {
return Math.abs(toRow - fromRow) <= 1 && Math.abs(toCol - fromCol) <= 1;
}
// Check if diagonal path is clear
function isDiagonalClear(fromRow, fromCol, toRow, toCol) {
const rowStep = toRow > fromRow ? 1 : -1;
const colStep = toCol > fromCol ? 1 : -1;
let row = fromRow + rowStep;
let col = fromCol + colStep;
while (row !== toRow) {
if (board[row][col]) return false;
row += rowStep;
col += colStep;
}
return true;
}
// Check if straight path is clear
function isStraightClear(fromRow, fromCol, toRow, toCol) {
const rowStep = toRow > fromRow ? 1 : toRow < fromRow ? -1 : 0;
const colStep = toCol > fromCol ? 1 : toCol < fromCol ? -1 : 0;
let row = fromRow + rowStep;
let col = fromCol + colStep;
while (row !== toRow || col !== toCol) {
if (board[row][col]) return false;
row += rowStep;
col += colStep;
}
return true;
}
// Make a move
function makeMove(fromRow, fromCol, toRow, toCol) {
// Save the move for undo (if needed) and check validation
const tempBoard = board.map((row) => [...row]);
const piece = board[fromRow][fromCol];
const capturedPiece = board[toRow][toCol];
// Make the move
board[toRow][toCol] = piece;
board[fromRow][fromCol] = null;
// Check if the move leaves the king in check
if (isKingInCheck(currentPlayer)) {
// Undo the move
board = tempBoard;
return;
}
// Record the move
lastMove = {
from: { row: fromRow, col: fromCol },
to: { row: toRow, col: toCol },
};
const moveNotation = generateMoveNotation(
piece,
fromRow,
fromCol,
toRow,
toCol,
capturedPiece,
);
moveHistory.push(moveNotation);
updateMoveList();
// Switch players
currentPlayer = currentPlayer === "white" ? "black" : "white";
// Check for checkmate or check
if (isCheckmate(currentPlayer)) {
endGame(
`${currentPlayer === "white" ? "Black" : "White"} wins by checkmate!`,
);
} else if (isKingInCheck(currentPlayer)) {
document.getElementById("checkStatus").textContent = "Check!";
} else {
document.getElementById("checkStatus").textContent = "";
}
renderBoard();
updateStatus();
}
// Generate move notation
function generateMoveNotation(
piece,
fromRow,
fromCol,
toRow,
toCol,
capturedPiece,
) {
const files = "abcdefgh";
const ranks = "87654321";
let notation = "";
if (piece.toLowerCase() !== "p") {
notation += piece.toUpperCase();
}
if (capturedPiece) {
if (piece.toLowerCase() === "p") {
notation += files[fromCol];
}
notation += "x";
}
notation += files[toCol] + ranks[toRow];
// Check for check or checkmate
currentPlayer = currentPlayer === "white" ? "black" : "white";
if (isCheckmate(currentPlayer)) {
notation += "#";
} else if (isKingInCheck(currentPlayer)) {
notation += "+";
}
currentPlayer = currentPlayer === "white" ? "black" : "white";
return notation;
}
// Find king position
function findKing(player) {
const kingPiece = player === "white" ? "K" : "k";
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
if (board[row][col] === kingPiece) {
return { row, col };
}
}
}
return null;
}
// Check if king is in check
function isKingInCheck(player) {
const king = findKing(player);
if (!king) return false;
const opponent = player === "white" ? "black" : "white";
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
const piece = board[row][col];
if (
piece &&
((opponent === "white" && piece === piece.toUpperCase()) ||
(opponent === "black" && piece === piece.toLowerCase()))
) {
// Temporarily switch players to check if opponent can attack king
const temp = currentPlayer;
currentPlayer = opponent;
const canAttack = isValidMove(row, col, king.row, king.col);
currentPlayer = temp;
if (canAttack) return true;
}
}
}
return false;
}
// Check for checkmate
function isCheckmate(player) {
if (!isKingInCheck(player)) return false;
// Try all possible moves
for (let fromRow = 0; fromRow < 8; fromRow++) {
for (let fromCol = 0; fromCol < 8; fromCol++) {
const piece = board[fromRow][fromCol];
if (!piece) continue;
if (
(player === "white" && piece === piece.toUpperCase()) ||
(player === "black" && piece === piece.toLowerCase())
) {
for (let toRow = 0; toRow < 8; toRow++) {
for (let toCol = 0; toCol < 8; toCol++) {
if (isValidMove(fromRow, fromCol, toRow, toCol)) {
// Try the move
const tempBoard = board.map((row) => [...row]);
board[toRow][toCol] = board[fromRow][fromCol];
board[fromRow][fromCol] = null;
const stillInCheck = isKingInCheck(player);
// Undo the move
board = tempBoard;
if (!stillInCheck) return false;
}
}
}
}
}
}
return true;
}
// Timer functions
function startTimer() {
if (timerInterval) clearInterval(timerInterval);
timerInterval = setInterval(() => {
if (gameOver) {
clearInterval(timerInterval);
return;
}
if (currentPlayer === "white") {
whiteTime--;
if (whiteTime <= 0) {
endGame("Black wins on time!");
}
} else {
blackTime--;
if (blackTime <= 0) {
endGame("White wins on time!");
}
}
updateTimers();
}, 1000);
}
// Update timer displays
function updateTimers() {
const whiteMinutes = Math.floor(whiteTime / 60);
const whiteSeconds = whiteTime % 60;
const blackMinutes = Math.floor(blackTime / 60);
const blackSeconds = blackTime % 60;
const whiteTimerEl = document.getElementById("whiteTimer");
const blackTimerEl = document.getElementById("blackTimer");
whiteTimerEl.textContent = `⏱ White: ${whiteMinutes}:${whiteSeconds.toString().padStart(2, "0")}`;
blackTimerEl.textContent = `⏱ Black: ${blackMinutes}:${blackSeconds.toString().padStart(2, "0")}`;
// Update active timer
whiteTimerEl.classList.toggle("active", currentPlayer === "white");
blackTimerEl.classList.toggle("active", currentPlayer === "black");
// Warning when time is low
if (whiteTime < 30) whiteTimerEl.classList.add("warning");
if (blackTime < 30) blackTimerEl.classList.add("warning");
}
// Update game status
function updateStatus() {
const turnIndicator = document.getElementById("turnIndicator");
turnIndicator.textContent = `${currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1)}'s Turn`;
turnIndicator.style.background =
currentPlayer === "white" ? "#4CAF50" : "#333";
}
// Update move list
function updateMoveList() {
const moveListEl = document.getElementById("moveList");
moveListEl.innerHTML = "";
for (let i = 0; i < moveHistory.length; i += 2) {
const movePair = document.createElement("div");
movePair.className = "move-pair";
const moveNumber = document.createElement("span");
moveNumber.className = "move-number";
moveNumber.textContent = `${Math.floor(i / 2) + 1}.`;
const whiteMove = document.createElement("span");
whiteMove.textContent = moveHistory[i];
const blackMove = document.createElement("span");
blackMove.textContent = moveHistory[i + 1] || "";
movePair.appendChild(moveNumber);
movePair.appendChild(whiteMove);
movePair.appendChild(blackMove);
moveListEl.appendChild(movePair);
}
// Scroll to bottom
moveListEl.scrollTop = moveListEl.scrollHeight;
}
// End the game
function endGame(message) {
gameOver = true;
clearInterval(timerInterval);
const modal = document.getElementById("gameOverModal");
const title = document.getElementById("gameOverTitle");
const messageEl = document.getElementById("gameOverMessage");
if (message.includes("checkmate")) {
title.textContent = "Checkmate!";
} else {
title.textContent = "Game Over";
}
messageEl.textContent = message;
modal.classList.add("show");
}
// Reset the game
function resetGame() {
gameOver = false;
currentPlayer = "white";
selectedSquare = null;
moveHistory = [];
lastMove = null;
whiteTime = 300;
blackTime = 300;
document.getElementById("checkStatus").textContent = "";
document.getElementById("gameOverModal").classList.remove("show");
initializeGame();
}
// Initialize the game on load
initializeGame();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment