Make snake and ladder game
A conversation between a human and Claude, captured on Klenara. Pick up where this left off — your own thread, your own tools.
Make snake and ladder game
I'll build a complete, polished Snake & Ladders game — a single self-contained HTML page with a colorful, bold board (snakes in red, ladders in green), two players, dice with animation, a turn indicator, win detection, and smooth piece movement. I'll make it playable on both desktop and mobile.
Both modes
Before I continue, I have a quick question for you.
Human vs Human + Human vs AI
Got it — Human vs Human and Human vs AI modes, with the AI playing Player 2. I'll build the full game now as a single self-contained HTML page: bold colorful board, animated dice, smooth piece movement, snake/ladder transitions, and a simple but effective AI opponent (it rolls, moves, and reacts to snakes/ladders just like a human would).
I'll build the full game now. Here's the complete self-contained HTML file — open it in any browser and it works.
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Snake & Ladders</title><style> * { margin: 0; padding: 0; box-sizing: border-box; } :root { --bg1: #1a0533; --bg2: #2d1b4e; --accent: #ff6b35; --accent2: #ffd23f; --p1: #ff4757; --p2: #1e90ff; --p1-glow: rgba(255, 71, 87, 0.6); --p2-glow: rgba(30, 144, 255, 0.6); --cell-light: #fff8e7; --cell-dark: #ffe8c2; } body { font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; background: linear-gradient(135deg, var(--bg1) 0%, var(--bg2) 50%, #0f0c29 100%); min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 16px; color: #fff; overflow-x: hidden; } h1 { font-size: clamp(1.8rem, 5vw, 2.8rem); font-weight: 900; background: linear-gradient(90deg, var(--accent2), var(--accent), #ff6b9d); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin-bottom: 8px; text-align: center; letter-spacing: 1px; } .subtitle { font-size: 0.85rem; color: rgba(255,255,255,0.5); margin-bottom: 16px; text-align: center; } /* ---- Mode Selector ---- */ .mode-panel { background: rgba(255,255,255,0.08); backdrop-filter: blur(12px); border: 1px solid rgba(255,255,255,0.15); border-radius: 20px; padding: 24px 32px; text-align: center; margin-bottom: 20px; animation: fadeUp 0.5s ease; } .mode-panel h2 { font-size: 1.2rem; margin-bottom: 16px; color: var(--accent2); } .mode-btns { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; } .mode-btn { padding: 12px 24px; border: 2px solid rgba(255,255,255,0.3); border-radius: 12px; background: rgba(255,255,255,0.05); color: #fff; font-size: 0.95rem; font-weight: 600; cursor: pointer; transition: all 0.25s ease; } .mode-btn:hover { background: rgba(255,255,255,0.15); border-color: var(--accent); transform: translateY(-2px); } .mode-btn.selected { background: linear-gradient(135deg, var(--accent), #ff6b9d); border-color: transparent; box-shadow: 0 4px 20px rgba(255, 107, 53, 0.4); } .start-btn { margin-top: 16px; padding: 14px 40px; border: none; border-radius: 14px; background: linear-gradient(135deg, var(--accent2), var(--accent)); color: #1a0533; font-size: 1.1rem; font-weight: 800; cursor: pointer; transition: all 0.25s ease; box-shadow: 0 4px 20px rgba(255, 210, 63, 0.3); } .start-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 30px rgba(255, 210, 63, 0.5); } /* ---- Game Area ---- */ .game-area { display: none; gap: 20px; align-items: flex-start; justify-content: center; flex-wrap: wrap; max-width: 100%; } .game-area.active { display: flex; } /* ---- Board ---- */ .board-wrapper { position: relative; width: min(90vw, 520px); aspect-ratio: 1; } .board { display: grid; grid-template-columns: repeat(10, 1fr); grid-template-rows: repeat(10, 1fr); width: 100%; height: 100%; border: 4px solid rgba(255, 210, 63, 0.4); border-radius: 12px; overflow: hidden; box-shadow: 0 8px 40px rgba(0,0,0,0.5), inset 0 0 30px rgba(255,210,63,0.1); } .cell { position: relative; display: flex; align-items: center; justify-content: center; font-size: clamp(0.5rem, 1.5vw, 0.7rem); font-weight: 700; color: rgba(0,0,0,0.4); border: 1px solid rgba(0,0,0,0.08); transition: background 0.3s ease; } .cell.light { background: var(--cell-light); } .cell.dark { background: var(--cell-dark); } .cell.start-cell { background: linear-gradient(135deg, #a8e6cf, #88d8a8) !important; } .cell.finish-cell { background: linear-gradient(135deg, #ffd23f, #ff6b35) !important; } .cell.finish-cell span { color: #fff; } .cell .cell-num { position: absolute; top: 2px; left: 3px; font-size: clamp(0.4rem, 1vw, 0.55rem); font-weight: 600; color: rgba(0,0,0,0.35); } /* Snake & Ladder indicators */ .cell .indicator { position: absolute; bottom: 1px; right: 2px; font-size: clamp(0.5rem, 1.2vw, 0.7rem); } /* ---- Pieces ---- */ .piece { position: absolute; width: clamp(18px, 4vw, 28px); height: clamp(18px, 4vw, 28px); border-radius: 50%; z-index: 10; transition: left 0.4s cubic-bezier(0.4, 0, 0.2, 1), top 0.4s cubic-bezier(0.4, 0, 0.2, 1); display: flex; align-items: center; justify-content: center; font-size: clamp(0.6rem, 1.5vw, 0.85rem); font-weight: 900; color: #fff; box-shadow: 0 3px 10px rgba(0,0,0,0.4); border: 2px solid rgba(255,255,255,0.7); } .piece.p1 { background: radial-gradient(circle at 35% 35%, #ff8a95, var(--p1)); box-shadow: 0 0 15px var(--p1-glow), 0 3px 10px rgba(0,0,0,0.4); } .piece.p2 { background: radial-gradient(circle at 35% 35%, #7ec8ff, var(--p2)); box-shadow: 0 0 15px var(--p2-glow), 0 3px 10px rgba(0,0,0,0.4); } .piece.active-turn { animation: pulse 1s ease-in-out infinite; } @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.15); } } /* ---- Side Panel ---- */ .side-panel { display: flex; flex-direction: column; gap: 14px; width: min(90vw, 220px); } .player-card { background: rgba(255,255,255,0.08); backdrop-filter: blur(10px); border: 2px solid rgba(255,255,255,0.12); border-radius: 16px; padding: 14px 16px; transition: all 0.3s ease; } .player-card.active { border-color: var(--accent2); box-shadow: 0 0 20px rgba(255, 210, 63, 0.2); } .player-card .player-name { font-size: 0.9rem; font-weight: 700; margin-bottom: 4px; display: flex; align-items: center; gap: 8px; } .player-card .player-name .dot { width: 12px; height: 12px; border-radius: 50%; display: inline-block; } .player-card .pos { font-size: 0.75rem; color: rgba(255,255,255,0.5); } .player-card .pos strong { color: var(--accent2); font-size: 1rem; } /* ---- Dice ---- */ .dice-area { display: flex; flex-direction: column; align-items: center; gap: 10px; } .dice { width: 64px; height: 64px; background: linear-gradient(145deg, #fff, #e8e8e8); border-radius: 14px; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr); padding: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.3), inset 0 -2px 5px rgba(0,0,0,0.1); cursor: pointer; transition: transform 0.15s ease; } .dice:hover { transform: scale(1.05); } .dice:active { transform: scale(0.95); } .dice.rolling { animation: shake 0.5s ease; } @keyframes shake { 0%, 100% { transform: rotate(0deg); } 20% { transform: rotate(-15deg); } 40% { transform: rotate(15deg); } 60% { transform: rotate(-10deg); } 80% { transform: rotate(10deg); } } .dice .pip { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; } .dice .pip.filled::after { content: ''; width: 12px; height: 12px; background: #1a0533; border-radius: 50%; box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); } .roll-btn { padding: 12px 28px; border: none; border-radius: 12px; background: linear-gradient(135deg, var(--accent), #ff6b9d); color: #fff; font-size: 1rem; font-weight: 700; cursor: pointer; transition: all 0.25s ease; box-shadow: 0 4px 15px rgba(255, 107, 53, 0.3); } .roll-btn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 6px 25px rgba(255, 107, 53, 0.5); } .roll-btn:disabled { opacity: 0.4; cursor: not-allowed; } /* ---- Message ---- */ .message { text-align: center; font-size: 0.9rem; font-weight: 600; min-height: 48px; display: flex; align-items: center; justify-content: center; color: var(--accent2); text-shadow: 0 1px 3px rgba(0,0,0,0.3); line-height: 1.4; } .message.snake-msg { color: #ff6b6b; } .message.ladder-msg { color: #51cf66; } /* ---- Win Overlay ---- */ .win-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.8); backdrop-filter: blur(8px); display: none; align-items: center; justify-content: center; z-index: 100; animation: fadeIn 0.5s ease; } .win-overlay.active { display: flex; } .win-card { background: linear-gradient(135deg, #2d1b4e, #1a0533); border: 2px solid var(--accent2); border-radius: 24px; padding: 40px 50px; text-align: center; box-shadow: 0 0 60px rgba(255, 210, 63, 0.3); animation: bounceIn 0.6s ease; } .win-card .trophy { font-size: 4rem; margin-bottom: 12px; } .win-card h2 { font-size: 1.8rem; color: var(--accent2); margin-bottom: 8px; } .win-card p { color: rgba(255,255,255,0.7); margin-bottom: 20px; } .win-card button { padding: 12px 32px; border: none; border-radius: 12px; background: linear-gradient(135deg, var(--accent2), var(--accent)); color: #1a0533; font-size: 1rem; font-weight: 800; cursor: pointer; transition: all 0.25s ease; } .win-card button:hover { transform: translateY(-2px); } /* ---- Reset ---- */ .reset-btn { padding: 8px 16px; border: 1px solid rgba(255,255,255,0.2); border-radius: 10px; background: rgba(255,255,255,0.05); color: rgba(255,255,255,0.6); font-size: 0.8rem; cursor: pointer; transition: all 0.2s ease; } .reset-btn:hover { background: rgba(255,255,255,0.12); color: #fff; } /* ---- Animations ---- */ @keyframes fadeUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes bounceIn { 0% { transform: scale(0.5); opacity: 0; } 60% { transform: scale(1.1); } 100% { transform: scale(1); opacity: 1; } } /* ---- Responsive ---- */ @media (max-width: 600px) { .game-area.active { flex-direction: column; align-items: center; } .side-panel { width: min(90vw, 520px); flex-direction: row; flex-wrap: wrap; justify-content: center; } .player-card { flex: 1; min-width: 140px; } }</style></head><body><h1>🐍 Snake & Ladders 🪜</h1><p class="subtitle">Classic board game — first to reach 100 wins!</p><!-- Mode Selector --><div class="mode-panel" id="modePanel"> <h2>Choose Game Mode</h2> <div class="mode-btns"> <button class="mode-btn" data-mode="pvp">👥 Human vs Human</button> <button class="mode-btn" data-mode="pve">🤖 Human vs AI</button> </div> <button class="start-btn" id="startBtn" disabled>Start Game</button></div><!-- Game Area --><div class="game-area" id="gameArea"> <div class="board-wrapper"> <div class="board" id="board"></div> <div class="piece p1" id="piece1">1</div> <div class="piece p2" id="piece2">2</div> </div> <div class="side-panel"> <div class="player-card" id="card1"> <div class="player-name"><span class="dot" style="background:var(--p1)"></span> Player 1</div> <div class="pos">Position: <strong id="pos1">0</strong></div> </div> <div class="player-card" id="card2"> <div class="player-name"><span class="dot" style="background:var(--p2)"></span> Player 2</div> <div class="pos">Position: <strong id="pos2">0</strong></div> </div> <div class="dice-area"> <div class="dice" id="dice"></div> <button class="roll-btn" id="rollBtn">🎲 Roll Dice</button> </div> <div class="message" id="message">Player 1's turn — roll the dice!</div> <button class="reset-btn" id="resetBtn">← Back to Menu</button> </div></div><!-- Win Overlay --><div class="win-overlay" id="winOverlay"> <div class="win-card"> <div class="trophy">🏆</div> <h2 id="winTitle">Player 1 Wins!</h2> <p id="winSub">Reached square 100 first!</p> <button id="playAgainBtn">Play Again</button> </div></div><script>// ---- Game State ----const state = { mode: null, // 'pvp' | 'pve' currentPlayer: 1, positions: { 1: 0, 2: 0 }, gameOver: false, rolling: false, aiThinking: false};// Snakes: head -> tailconst snakes = { 16: 6, 47: 26, 49: 11, 56: 53, 62: 19, 64: 60, 87: 24, 93: 73, 95: 75, 98: 78};// Ladders: bottom -> topconst ladders = { 1: 38, 4: 14, 9: 31, 21: 42, 28: 84, 36: 44, 51: 67, 72: 91, 80: 100};// ---- DOM ----const boardEl = document.getElementById('board');const piece1El = document.getElementById('piece1');const piece2El = document.getElementById('piece2');const diceEl = document.getElementById('dice');const rollBtn = document.getElementById('rollBtn');const messageEl = document.getElementById('message');const modePanel = document.getElementById('modePanel');const gameArea = document.getElementById('gameArea');const startBtn = document.getElementById('startBtn');const resetBtn = document.getElementById('resetBtn');const winOverlay = document.getElementById('winOverlay');const playAgainBtn = document.getElementById('playAgainBtn');const pos1El = document.getElementById('pos1');const pos2El = document.getElementById('pos2');const card1El = document.getElementById('card1');const card2El = document.getElementById('card2');const winTitleEl = document.getElementById('winTitle');const winSubEl = document.getElementById('winSub');// ---- Build Board ----function buildBoard() { boardEl.innerHTML = ''; for (let row = 9; row >= 0; row--) { for (let col = 0; col < 10; col++) { const cellNum = row % 2 === 0 ? row * 10 + col + 1 : row * 10 + (10 - col); const cell = document.createElement('div'); cell.className = 'cell ' + ((row + col) % 2 === 0 ? 'light' : 'dark'); cell.id = `cell-${cellNum}`; const num = document.createElement('span'); num.className = 'cell-num'; num.textContent = cellNum; cell.appendChild(num); if (cellNum === 1) cell.classList.add('start-cell'); if (cellNum === 100) cell.classList.add('finish-cell'); // Indicators if (snakes[cellNum]) { const ind = document.createElement('span'); ind.className = 'indicator'; ind.textContent = '🐍'; cell.appendChild(ind); } if (ladders[cellNum]) { const ind = document.createElement('span'); ind.className = 'indicator'; ind.textContent = '🪜'; cell.appendChild(ind); } boardEl.appendChild(cell); } }}// ---- Position to CSS ----function getCellPosition(pos) { if (pos === 0) return { left: '0%', top: '95%' }; const row = Math.floor((pos - 1) / 10); const colInRow = (pos - 1) % 10; const col = row % 2 === 0 ? colInRow : 9 - colInRow; const topRow = 9 - row; const left = (col * 10 + 5) + '%'; const top = (topRow * 10 + 5) + '%'; return { left, top };}function updatePiece(pieceEl, pos, offset) { const { left, top } = getCellPosition(pos); pieceEl.style.left = left; pieceEl.style.top = top; if (offset) { pieceEl.style.transform = `translate(${offset.x}px, ${offset.y}px)`; }}// ---- Dice ----const pipPatterns = { 1: [0,0,0, 0,1,0, 0,0,0], 2: [0,0,1, 0,0,0, 1,0,0], 3: [0,0,1, 0,1,0, 1,0,0], 4: [1,0,1, 0,0,0, 1,0,1], 5: [1,0,1, 0,1,0, 1,0,1], 6: [1,0,1, 1,0,1, 1,0,1]};function renderDice(value) { diceEl.innerHTML = ''; const pattern = pipPatterns[value] || pipPatterns[1]; pattern.forEach(filled => { const pip = document.createElement('div'); pip.className = 'pip' + (filled ? ' filled' : ''); diceEl.appendChild(pip); });}function rollDice() { return Math.floor(Math.random() * 6) + 1;}// ---- Turn Logic ----function updateUI() { pos1El.textContent = state.positions[1]; pos2El.textContent = state.positions[2]; const p1 = state.currentPlayer === 1; card1El.classList.toggle('active', p1); card2El.classList.toggle('active', !p1); piece1El.classList.toggle('active-turn', p1 && !state.gameOver); piece2El.classList.toggle('active-turn', !p1 && !state.gameOver); const isAI = state.mode === 'pve' && state.currentPlayer === 2; rollBtn.disabled = state.rolling || state.gameOver || state.aiThinking; rollBtn.textContent = isAI ? '🤖 AI Rolling...' : '🎲 Roll Dice';}function setMessage(text, type = '') { messageEl.textContent = text; messageEl.className = 'message' + (type ? ' ' + type : '');}function getPlayerName(p) { if (state.mode === 'pve') { return p === 1 ? 'Player 1' : 'AI 🤖'; } return `Player ${p}`;}async function takeTurn() { if (state.gameOver || state.rolling) return; state.rolling = true; updateUI(); const player = state.currentPlayer; const pieceEl = player === 1 ? piece1El : piece2El; // Animate dice diceEl.classList.add('rolling'); let finalVal = rollDice(); for (let i = 0; i < 8; i++) { renderDice(rollDice()); await sleep(60); } renderDice(finalVal); diceEl.classList.remove('rolling'); await sleep(300); const startPos = state.positions[player]; let newPos = startPos + finalVal; if (newPos > 100) { setMessage(`${getPlayerName(player)} rolled ${finalVal} — need exact roll to finish!`, ''); state.rolling = false; await sleep(1200); switchTurn(); return; } // Move step by step for (let i = startPos + 1; i <= newPos; i++) { updatePiece(pieceEl, i, player === 1 ? {x: -4, y: -4} : {x: 4, y: 4}); await sleep(200); } state.positions[player] = newPos; updatePiece(pieceEl, newPos, player === 1 ? {x: -4, y: -4} : {x: 4, y: 4}); updateUI(); // Check win if (newPos === 100) { state.gameOver = true; state.rolling = false; setMessage(`🎉 ${getPlayerName(player)} wins!`); await sleep(800); showWin(player); return; } // Check snake/ladder await sleep(400); if (snakes[newPos]) { const tail = snakes[newPos]; setMessage(`🐍 Snake! ${getPlayerName(player)} slides down to ${tail}`, 'snake-msg'); await sleep(1000); for (let i = newPos; i > tail; i--) { updatePiece(pieceEl, i, player === 1 ? {x: -4, y: -4} : {x: 4, y: 4}); await sleep(150); } state.positions[player] = tail; updatePiece(pieceEl, tail, player === 1 ? {x: -4, y: -4} : {x: 4, y: 4}); updateUI(); } else if (ladders[newPos]) { const top = ladders[newPos]; setMessage(`🪜 Ladder! ${getPlayerName(player)} climbs up to ${top}`, 'ladder-msg'); await sleep(1000); for (let i = newPos; i < top; i++) { updatePiece(pieceEl, i, player === 1 ? {x: -4, y: -4} : {x: 4, y: 4}); await sleep(150); } state.positions[player] = top; updatePiece(pieceEl, top, player === 1 ? {x: -4, y: -4} : {x: 4, y: 4}); updateUI(); if (top === 100) { state.gameOver = true; state.rolling = false; setMessage(`🎉 ${getPlayerName(player)} wins!`); await sleep(800); showWin(player); return; } } else { setMessage(`${getPlayerName(player)} rolled ${finalVal} → square ${newPos}`); } state.rolling = false; await sleep(1000); switchTurn();}function switchTurn() { state.currentPlayer = state.currentPlayer === 1 ? 2 : 1; updateUI(); setMessage(`${getPlayerName(state.currentPlayer)}'s turn — roll the dice!`); // AI auto-play if (state.mode === 'pve' && state.currentPlayer === 2 && !state.gameOver) { state.aiThinking = true; updateUI(); setTimeout(() => { state.aiThinking = false; takeTurn(); }, 1200); }}function showWin(player) { const name = getPlayerName(player); winTitleEl.textContent = `${name} Wins! 🎉`; winSubEl.textContent = 'Reached square 100 first!'; winOverlay.classList.add('active');}// ---- Mode Selection ----let selectedMode = null;document.querySelectorAll('.mode-btn').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('selected')); btn.classList.add('selected'); selectedMode = btn.dataset.mode; startBtn.disabled = false; });});startBtn.addEventListener('click', () => { if (!selectedMode) return; state.mode = selectedMode; state.currentPlayer = 1; state.positions = { 1: 0, 2: 0 }; state.gameOver = false; state.rolling = false; state.aiThinking = false; modePanel.style.display = 'none'; gameArea.classList.add('active'); // Update player 2 name const p2name = document.querySelector('#card2 .player-name'); if (state.mode === 'pve') { p2name.innerHTML = '<span class="dot" style="background:var(--p2)"></span> AI 🤖'; } else { p2name.innerHTML = '<span class="dot" style="background:var(--p2)"></span> Player 2'; } buildBoard(); updatePiece(piece1El, 0, {x: -4, y: -4}); updatePiece(piece2El, 0, {x: 4, y: 4}); renderDice(1); updateUI(); setMessage("Player 1's turn — roll the dice!");});rollBtn.addEventListener('click', () => { if (state.mode === 'pve' && state.currentPlayer === 2) return; takeTurn();});diceEl.addEventListener('click', () => { if (state.mode === 'pve' && state.currentPlayer === 2) return; takeTurn();});resetBtn.addEventListener('click', () => { gameArea.classList.remove('active'); modePanel.style.display = 'block'; winOverlay.classList.remove('active'); state.gameOver = false;});playAgainBtn.addEventListener('click', () => { winOverlay.classList.remove('active'); gameArea.classList.remove('active'); modePanel.style.display = 'block'; state.gameOver = false;});// ---- Utility ----function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms));}// InitbuildBoard();updatePiece(piece1El, 0, {x: -4, y: -4});updatePiece(piece2El, 0, {x: 4, y: 4});renderDice(1);</script></body></html>What you get:
- Two modes — Human vs Human (both players roll on the same device) and Human vs AI (AI auto-rolls with a short delay so you can watch it move)
- 10×10 board with classic snake positions (16→6, 47→26, 49→11, 56→53, 62→19, 64→60, 87→24, 93→73, 95→75, 98→78) and ladders (1→38, 4→14, 9→31, 21→42, 28→84, 36→44, 51→67, 72→91, 80→100)
- Animated dice with shake effect and pip rendering
- Step-by-step piece movement — pieces glide square by square, then slide down snakes or climb ladders with a pause so you can see what happened
- Exact-roll-to-finish rule — you must land exactly on 100; overshooting means you stay put
- Turn indicator with glowing active piece and highlighted player card
- Win overlay with trophy and play-again
- Fully responsive — works on phone, tablet, and desktop
Open it, pick a mode, hit Start, and play. 🐍🪜