| <html lang="id"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>🎲 Game Ular Tangga</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| padding: 20px; | |
| color: #fff; | |
| } | |
| h1 { | |
| font-size: 2.5em; | |
| margin-bottom: 10px; | |
| text-shadow: 3px 3px 6px rgba(0,0,0,0.3); | |
| text-align: center; | |
| } | |
| .subtitle { | |
| margin-bottom: 20px; | |
| opacity: 0.9; | |
| text-align: center; | |
| } | |
| .game-container { | |
| display: flex; | |
| gap: 20px; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| max-width: 1100px; | |
| width: 100%; | |
| } | |
| /* Board */ | |
| .board-wrapper { | |
| background: rgba(255,255,255,0.95); | |
| border-radius: 15px; | |
| padding: 15px; | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.3); | |
| } | |
| .board { | |
| display: grid; | |
| grid-template-columns: repeat(10, 60px); | |
| grid-template-rows: repeat(10, 60px); | |
| gap: 1px; | |
| background: #2c3e50; | |
| border: 3px solid #2c3e50; | |
| border-radius: 8px; | |
| position: relative; | |
| } | |
| .cell { | |
| background: #f8f4e8; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-weight: bold; | |
| font-size: 14px; | |
| color: #555; | |
| position: relative; | |
| border-radius: 2px; | |
| transition: background 0.4s, transform 0.3s; | |
| } | |
| .cell.even { background: #fdf6e3; } | |
| .cell.start { background: #b8e6b8; } | |
| .cell.end { background: #ffd700; } | |
| .cell.snake-head { background: #ffb3b3; } | |
| .cell.ladder-bottom { background: #b3d9ff; } | |
| /* Highlight saat pion melewati */ | |
| .cell.step-highlight { | |
| background: #fff59d ; | |
| box-shadow: inset 0 0 15px rgba(255, 193, 7, 0.8); | |
| transform: scale(1.08); | |
| z-index: 5; | |
| } | |
| .cell.snake-target { | |
| background: #ff8a80 ; | |
| box-shadow: inset 0 0 15px rgba(244, 67, 54, 0.6); | |
| } | |
| .cell.ladder-target { | |
| background: #80d8ff ; | |
| box-shadow: inset 0 0 15px rgba(33, 150, 243, 0.6); | |
| } | |
| .cell .icon { | |
| font-size: 22px; | |
| position: absolute; | |
| opacity: 0.7; | |
| transition: transform 0.3s; | |
| } | |
| .cell.step-highlight .icon, | |
| .cell.snake-target .icon, | |
| .cell.ladder-target .icon { | |
| transform: scale(1.3); | |
| } | |
| .cell .num { | |
| position: relative; | |
| z-index: 1; | |
| } | |
| /* Pion */ | |
| .pawn { | |
| width: 24px; | |
| height: 24px; | |
| border-radius: 50%; | |
| position: absolute; | |
| border: 2px solid white; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.4); | |
| z-index: 10; | |
| /* TRANSISI UTAMA: gerakan antar kotak */ | |
| transition: left 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), | |
| top 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), | |
| transform 0.35s ease, | |
| box-shadow 0.3s; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 13px; | |
| color: white; | |
| font-weight: bold; | |
| } | |
| .pawn.p1 { | |
| background: radial-gradient(circle at 30% 30%, #ff7961, #c0392b); | |
| top: 6px; left: 6px; | |
| } | |
| .pawn.p2 { | |
| background: radial-gradient(circle at 30% 30%, #74b9ff, #2980b9); | |
| top: 6px; right: 6px; | |
| } | |
| .pawn.active { | |
| box-shadow: 0 0 12px 4px rgba(255, 235, 59, 0.7), 0 2px 4px rgba(0,0,0,0.4); | |
| } | |
| /* Animasi lompatan per langkah */ | |
| @keyframes hop { | |
| 0% { transform: translateY(0) scale(1); } | |
| 45% { transform: translateY(-14px) scale(1.2); } | |
| 90% { transform: translateY(0) scale(0.95); } | |
| 100% { transform: translateY(0) scale(1); } | |
| } | |
| .pawn.hopping { | |
| animation: hop 0.4s ease-out; | |
| z-index: 100; | |
| } | |
| /* Trail effect saat melompat */ | |
| @keyframes trail { | |
| 0% { opacity: 0.6; transform: scale(1); } | |
| 100% { opacity: 0; transform: scale(0.4); } | |
| } | |
| .trail { | |
| position: absolute; | |
| width: 24px; | |
| height: 24px; | |
| border-radius: 50%; | |
| z-index: 5; | |
| pointer-events: none; | |
| animation: trail 0.5s ease-out forwards; | |
| } | |
| .trail.p1 { background: rgba(231, 76, 60, 0.6); top: 6px; left: 6px; } | |
| .trail.p2 { background: rgba(52, 152, 219, 0.6); top: 6px; right: 6px; } | |
| /* Animasi tergigit ular: turun sambil berputar */ | |
| @keyframes snakeBite { | |
| 0% { transform: translate(0, 0) rotate(0deg) scale(1); } | |
| 25% { transform: translate(0, -20px) rotate(180deg) scale(1.3); } | |
| 50% { transform: translate(0, 0) rotate(360deg) scale(0.8); filter: hue-rotate(60deg); } | |
| 75% { transform: translate(0, 20px) rotate(540deg) scale(0.6); } | |
| 100% { transform: translate(0, 0) rotate(720deg) scale(1); filter: hue-rotate(0deg); } | |
| } | |
| .pawn.snake-bite { | |
| animation: snakeBite 1s ease-in-out; | |
| z-index: 100; | |
| } | |
| /* Animasi naik tangga: terbang naik */ | |
| @keyframes ladderClimb { | |
| 0% { transform: translateY(0) scale(1); } | |
| 40% { transform: translateY(-30px) scale(1.3); } | |
| 70% { transform: translateY(-15px) scale(1.1) rotate(10deg); } | |
| 100% { transform: translateY(0) scale(1) rotate(0deg); } | |
| } | |
| .pawn.ladder-climb { | |
| animation: ladderClimb 0.9s ease-out; | |
| z-index: 100; | |
| } | |
| /* Efek kilau saat menang */ | |
| @keyframes winGlow { | |
| 0%, 100% { box-shadow: 0 0 15px 5px gold, 0 0 25px 10px rgba(255,215,0,0.5); } | |
| 50% { box-shadow: 0 0 25px 10px gold, 0 0 40px 20px rgba(255,215,0,0.8); } | |
| } | |
| .pawn.winner { | |
| animation: winGlow 1s ease-in-out infinite; | |
| } | |
| /* Side Panel */ | |
| .side-panel { | |
| background: rgba(255,255,255,0.95); | |
| border-radius: 15px; | |
| padding: 20px; | |
| width: 280px; | |
| color: #333; | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.3); | |
| height: fit-content; | |
| } | |
| .player-info { | |
| background: #f5f5f5; | |
| padding: 12px; | |
| border-radius: 8px; | |
| margin-bottom: 10px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| transition: all 0.3s; | |
| } | |
| .player-info.active { | |
| background: #fff3cd; | |
| border: 2px solid #ffc107; | |
| transform: scale(1.02); | |
| } | |
| .player-name { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-weight: bold; | |
| } | |
| .player-dot { | |
| width: 16px; | |
| height: 16px; | |
| border-radius: 50%; | |
| } | |
| .dot-p1 { background: #e74c3c; } | |
| .dot-p2 { background: #3498db; } | |
| .dice-area { | |
| text-align: center; | |
| padding: 20px 0; | |
| margin: 15px 0; | |
| background: linear-gradient(135deg, #f5f7fa, #c3cfe2); | |
| border-radius: 10px; | |
| } | |
| .dice { | |
| width: 80px; | |
| height: 80px; | |
| background: white; | |
| border-radius: 12px; | |
| margin: 0 auto 15px; | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| grid-template-rows: repeat(3, 1fr); | |
| padding: 10px; | |
| box-shadow: 0 5px 15px rgba(0,0,0,0.2); | |
| transition: transform 0.3s; | |
| } | |
| .dice.rolling { | |
| animation: rollDice 0.5s ease-in-out; | |
| } | |
| @keyframes rollDice { | |
| 0% { transform: rotate(0deg) scale(1); } | |
| 50% { transform: rotate(180deg) scale(1.2); } | |
| 100% { transform: rotate(360deg) scale(1); } | |
| } | |
| .dot { | |
| width: 12px; | |
| height: 12px; | |
| background: #2c3e50; | |
| border-radius: 50%; | |
| align-self: center; | |
| justify-self: center; | |
| } | |
| .dot.hidden { visibility: hidden; } | |
| .roll-btn { | |
| background: linear-gradient(135deg, #e74c3c, #c0392b); | |
| color: white; | |
| border: none; | |
| padding: 12px 30px; | |
| border-radius: 25px; | |
| font-size: 16px; | |
| font-weight: bold; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| box-shadow: 0 4px 10px rgba(0,0,0,0.2); | |
| width: 100%; | |
| } | |
| .roll-btn:hover:not(:disabled) { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 15px rgba(0,0,0,0.3); | |
| } | |
| .roll-btn:disabled { | |
| background: #95a5a6; | |
| cursor: not-allowed; | |
| opacity: 0.7; | |
| } | |
| .message { | |
| background: #ecf0f1; | |
| padding: 12px; | |
| border-radius: 8px; | |
| text-align: center; | |
| font-weight: bold; | |
| min-height: 50px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin-top: 15px; | |
| color: #2c3e50; | |
| transition: background 0.3s; | |
| } | |
| .message.win { | |
| background: linear-gradient(135deg, #f6d365, #fda085); | |
| color: #8b4513; | |
| font-size: 18px; | |
| animation: pulse 1.2s ease-in-out infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| } | |
| .reset-btn { | |
| background: #95a5a6; | |
| color: white; | |
| border: none; | |
| padding: 10px 20px; | |
| border-radius: 20px; | |
| font-size: 14px; | |
| cursor: pointer; | |
| width: 100%; | |
| margin-top: 10px; | |
| transition: background 0.3s; | |
| } | |
| .reset-btn:hover { background: #7f8c8d; } | |
| .history { | |
| margin-top: 15px; | |
| max-height: 150px; | |
| overflow-y: auto; | |
| background: #f8f9fa; | |
| padding: 10px; | |
| border-radius: 8px; | |
| font-size: 13px; | |
| } | |
| .history-item { | |
| padding: 5px; | |
| border-bottom: 1px solid #e0e0e0; | |
| color: #555; | |
| } | |
| .history-item:last-child { border-bottom: none; } | |
| @media (max-width: 768px) { | |
| .board { | |
| grid-template-columns: repeat(10, 40px); | |
| grid-template-rows: repeat(10, 40px); | |
| } | |
| .cell { font-size: 11px; } | |
| .cell .icon { font-size: 16px; } | |
| h1 { font-size: 1.8em; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>🐍🪜 Game Ular Tangga 🪜🐍</h1> | |
| <p class="subtitle">Lempar dadu dan raih kotak 100 untuk menang!</p> | |
| <div class="game-container"> | |
| <!-- Board --> | |
| <div class="board-wrapper"> | |
| <div class="board" id="board"></div> | |
| </div> | |
| <!-- Side Panel --> | |
| <div class="side-panel"> | |
| <h3 style="margin-bottom: 15px; text-align: center;">🎮 Pemain</h3> | |
| <div class="player-info" id="player1-info"> | |
| <div class="player-name"> | |
| <span class="player-dot dot-p1"></span> | |
| <span>Pemain 1 (Merah)</span> | |
| </div> | |
| <span>📍 <span id="pos1">1</span></span> | |
| </div> | |
| <div class="player-info" id="player2-info"> | |
| <div class="player-name"> | |
| <span class="player-dot dot-p2"></span> | |
| <span>Pemain 2 (Biru)</span> | |
| </div> | |
| <span>📍 <span id="pos2">1</span></span> | |
| </div> | |
| <div class="dice-area"> | |
| <div class="dice" id="dice"> | |
| <div class="dot"></div><div class="dot hidden"></div><div class="dot"></div> | |
| <div class="dot hidden"></div><div class="dot"></div><div class="dot hidden"></div> | |
| <div class="dot"></div><div class="dot hidden"></div><div class="dot"></div> | |
| </div> | |
| <button class="roll-btn" id="rollBtn">🎲 Lempar Dadu</button> | |
| </div> | |
| <div class="message" id="message">Giliran Pemain 1</div> | |
| <div class="history" id="history"> | |
| <div class="history-item">🎮 Game dimulai!</div> | |
| </div> | |
| <button class="reset-btn" id="resetBtn">🔄 Mulai Ulang</button> | |
| </div> | |
| </div> | |
| <script> | |
| // === Konfigurasi Ular dan Tangga === | |
| const snakes = { | |
| 17: 4, 25: 5, 34: 1, 46: 20, 54: 36, | |
| 62: 43, 64: 60, 74: 52, 89: 51, 95: 72, 99: 27 | |
| }; | |
| const ladders = { | |
| 3: 22, 6: 25, 11: 40, 15: 33, 20: 38, | |
| 27: 73, 36: 57, 51: 67, 71: 91, 80: 99 | |
| }; | |
| // === State Game === | |
| const state = { | |
| positions: [1, 1], | |
| currentPlayer: 0, | |
| gameOver: false, | |
| animating: false | |
| }; | |
| // === Util === | |
| function sleep(ms) { | |
| return new Promise(r => setTimeout(r, ms)); | |
| } | |
| // === Inisialisasi Board === | |
| const boardEl = document.getElementById('board'); | |
| const cells = {}; | |
| function buildBoard() { | |
| boardEl.innerHTML = ''; | |
| cells.length = 0; | |
| for (let row = 9; row >= 0; row--) { | |
| for (let col = 0; col < 10; col++) { | |
| let num; | |
| if (row % 2 === 0) { | |
| num = row * 10 + (10 - col); | |
| } else { | |
| num = row * 10 + col + 1; | |
| } | |
| const cell = document.createElement('div'); | |
| cell.className = 'cell'; | |
| cell.id = 'cell-' + num; | |
| cell.dataset.num = num; | |
| if (num === 1) cell.classList.add('start'); | |
| if (num === 100) cell.classList.add('end'); | |
| if (snakes[num]) cell.classList.add('snake-head'); | |
| if (ladders[num]) cell.classList.add('ladder-bottom'); | |
| const numEl = document.createElement('span'); | |
| numEl.className = 'num'; | |
| numEl.textContent = num; | |
| cell.appendChild(numEl); | |
| if (snakes[num]) { | |
| const icon = document.createElement('span'); | |
| icon.className = 'icon'; | |
| icon.textContent = '🐍'; | |
| cell.appendChild(icon); | |
| } else if (ladders[num]) { | |
| const icon = document.createElement('span'); | |
| icon.className = 'icon'; | |
| icon.textContent = '🪜'; | |
| cell.appendChild(icon); | |
| } else if (num === 100) { | |
| const icon = document.createElement('span'); | |
| icon.className = 'icon'; | |
| icon.textContent = '🏆'; | |
| cell.appendChild(icon); | |
| } | |
| boardEl.appendChild(cell); | |
| cells[num] = cell; | |
| } | |
| } | |
| // Buat pion sekali (identitas tetap agar transisi mulus) | |
| state.positions.forEach((pos, idx) => { | |
| const cell = cells[pos]; | |
| const pawn = document.createElement('div'); | |
| pawn.className = 'pawn ' + (idx === 0 ? 'p1' : 'p2'); | |
| pawn.id = 'pawn-' + idx; | |
| pawn.textContent = idx === 0 ? '1' : '2'; | |
| cell.appendChild(pawn); | |
| }); | |
| } | |
| // === Animasi perpindahan pion === | |
| // appendChild ke sel baru -> CSS transition otomatis menganimasi | |
| function movePawnTo(idx, targetCell, animClass = null) { | |
| const pawn = document.getElementById('pawn-' + idx); | |
| if (animClass) pawn.classList.add(animClass); | |
| // Tambahkan trail di posisi lama | |
| const oldCell = pawn.parentElement; | |
| if (oldCell !== targetCell && animClass === 'hopping') { | |
| const trail = document.createElement('div'); | |
| trail.className = 'trail ' + (idx === 0 ? 'p1' : 'p2'); | |
| oldCell.appendChild(trail); | |
| setTimeout(() => trail.remove(), 500); | |
| } | |
| targetCell.appendChild(pawn); | |
| if (animClass) { | |
| setTimeout(() => pawn.classList.remove(animClass), 1000); | |
| } | |
| } | |
| function setActivePlayerHighlight() { | |
| document.getElementById('pawn-0').classList.toggle('active', | |
| state.currentPlayer === 0 && !state.gameOver && !state.animating); | |
| document.getElementById('pawn-1').classList.toggle('active', | |
| state.currentPlayer === 1 && !state.gameOver && !state.animating); | |
| } | |
| // === Dadu === | |
| function getDiceDots(value) { | |
| const patterns = { | |
| 1: [0,0,0, 0,1,0, 0,0,0], | |
| 2: [1,0,0, 0,0,0, 0,0,1], | |
| 3: [1,0,0, 0,1,0, 0,0,1], | |
| 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] | |
| }; | |
| return patterns[value]; | |
| } | |
| function updateDiceDisplay(value) { | |
| const dots = document.querySelectorAll('#dice .dot'); | |
| const pattern = getDiceDots(value); | |
| dots.forEach((dot, i) => { | |
| if (pattern[i]) dot.classList.remove('hidden'); | |
| else dot.classList.add('hidden'); | |
| }); | |
| } | |
| function rollDice() { | |
| if (state.gameOver || state.animating) return; | |
| const dice = document.getElementById('dice'); | |
| const rollBtn = document.getElementById('rollBtn'); | |
| rollBtn.disabled = true; | |
| dice.classList.add('rolling'); | |
| let counter = 0; | |
| const interval = setInterval(() => { | |
| const tempVal = Math.floor(Math.random() * 6) + 1; | |
| updateDiceDisplay(tempVal); | |
| counter++; | |
| if (counter > 8) { | |
| clearInterval(interval); | |
| const finalValue = Math.floor(Math.random() * 6) + 1; | |
| updateDiceDisplay(finalValue); | |
| dice.classList.remove('rolling'); | |
| movePlayer(finalValue); | |
| } | |
| }, 80); | |
| } | |
| // === Pergerakan Pemain (dengan animasi langkah demi langkah) === | |
| async function movePlayer(steps) { | |
| state.animating = true; | |
| const playerIdx = state.currentPlayer; | |
| const playerName = playerIdx === 0 ? 'Pemain 1' : 'Pemain 2'; | |
| addHistory(`🎲 ${playerName} melempar dadu: ${steps}`); | |
| // Harus tepat di 100 | |
| if (state.positions[playerIdx] + steps > 100) { | |
| addHistory(`⏸️ ${playerName} harus tepat di 100!`); | |
| setMessage(`⏸️ ${playerName} harus tepat di 100! Diam di kotak ${state.positions[playerIdx]}`); | |
| state.animating = false; | |
| await sleep(900); | |
| switchPlayer(); | |
| return; | |
| } | |
| // === ANIMASI LANGKAH DEMI LANGKAH === | |
| setMessage(`🚶 ${playerName} berjalan...`); | |
| for (let i = 1; i <= steps; i++) { | |
| const newPos = state.positions[playerIdx] + 1; | |
| state.positions[playerIdx] = newPos; | |
| // Highlight kotak tujuan | |
| const targetCell = cells[newPos]; | |
| targetCell.classList.add('step-highlight'); | |
| // Gerakkan pion dengan efek hop + trail | |
| movePawnTo(playerIdx, targetCell, 'hopping'); | |
| updatePlayerInfo(); | |
| // Update pesan per langkah (opsional, hanya langkah terakhir) | |
| if (i === steps) { | |
| setMessage(`➡️ ${playerName} sampai di kotak ${newPos}`); | |
| } | |
| await sleep(380); | |
| // Hapus highlight | |
| targetCell.classList.remove('step-highlight'); | |
| } | |
| const finalPos = state.positions[playerIdx]; | |
| addHistory(`✅ ${playerName} sampai di kotak ${finalPos}`); | |
| // === CEK ULAR / TANGGA === | |
| await sleep(300); | |
| if (snakes[finalPos]) { | |
| const dest = snakes[finalPos]; | |
| // Highlight source dan destination | |
| cells[finalPos].classList.add('snake-target'); | |
| cells[dest].classList.add('snake-target'); | |
| setMessage(`🐍 ${playerName} digigit ular di kotak ${finalPos}!`); | |
| addHistory(`🐍 ${playerName} tergigit ular! Turun dari ${finalPos} ke ${dest}`); | |
| await sleep(600); | |
| state.positions[playerIdx] = dest; | |
| movePawnTo(playerIdx, cells[dest], 'snake-bite'); | |
| updatePlayerInfo(); | |
| await sleep(1000); | |
| cells[finalPos].classList.remove('snake-target'); | |
| cells[dest].classList.remove('snake-target'); | |
| checkWin(); | |
| } else if (ladders[finalPos]) { | |
| const dest = ladders[finalPos]; | |
| cells[finalPos].classList.add('ladder-target'); | |
| cells[dest].classList.add('ladder-target'); | |
| setMessage(`🪜 ${playerName} menemukan tangga di kotak ${finalPos}!`); | |
| addHistory(`🪜 ${playerName} naik tangga! Dari ${finalPos} ke ${dest}`); | |
| await sleep(600); | |
| state.positions[playerIdx] = dest; | |
| movePawnTo(playerIdx, cells[dest], 'ladder-climb'); | |
| updatePlayerInfo(); | |
| await sleep(900); | |
| cells[finalPos].classList.remove('ladder-target'); | |
| cells[dest].classList.remove('ladder-target'); | |
| checkWin(); | |
| } else { | |
| checkWin(); | |
| } | |
| } | |
| function checkWin() { | |
| const playerIdx = state.currentPlayer; | |
| const playerName = playerIdx === 0 ? 'Pemain 1' : 'Pemain 2'; | |
| if (state.positions[playerIdx] === 100) { | |
| state.gameOver = true; | |
| state.animating = false; | |
| addHistory(`🏆 ${playerName} MENANG!`); | |
| setMessage(`🏆🎉 ${playerName} MENANG! 🎉🏆`, true); | |
| document.getElementById('rollBtn').disabled = true; | |
| document.getElementById('pawn-' + playerIdx).classList.add('winner'); | |
| celebrate(); | |
| return; | |
| } | |
| setTimeout(() => { | |
| state.animating = false; | |
| switchPlayer(); | |
| }, 600); | |
| } | |
| function switchPlayer() { | |
| state.currentPlayer = state.currentPlayer === 0 ? 1 : 0; | |
| const nextName = state.currentPlayer === 0 ? 'Pemain 1' : 'Pemain 2'; | |
| setMessage(`Giliran ${nextName}`); | |
| updatePlayerInfo(); | |
| setActivePlayerHighlight(); | |
| document.getElementById('rollBtn').disabled = false; | |
| } | |
| function updatePlayerInfo() { | |
| document.getElementById('pos1').textContent = state.positions[0]; | |
| document.getElementById('pos2').textContent = state.positions[1]; | |
| document.getElementById('player1-info').classList.toggle('active', | |
| state.currentPlayer === 0 && !state.gameOver); | |
| document.getElementById('player2-info').classList.toggle('active', | |
| state.currentPlayer === 1 && !state.gameOver); | |
| } | |
| function setMessage(text, isWin = false) { | |
| const msg = document.getElementById('message'); | |
| msg.textContent = text; | |
| msg.classList.toggle('win', isWin); | |
| } | |
| function addHistory(text) { | |
| const historyEl = document.getElementById('history'); | |
| const item = document.createElement('div'); | |
| item.className = 'history-item'; | |
| item.textContent = text; | |
| historyEl.insertBefore(item, historyEl.firstChild); | |
| if (historyEl.children.length > 20) { | |
| historyEl.removeChild(historyEl.lastChild); | |
| } | |
| } | |
| function resetGame() { | |
| state.positions = [1, 1]; | |
| state.currentPlayer = 0; | |
| state.gameOver = false; | |
| state.animating = false; | |
| const historyEl = document.getElementById('history'); | |
| historyEl.innerHTML = '<div class="history-item">🎮 Game dimulai ulang!</div>'; | |
| setMessage('Giliran Pemain 1'); | |
| document.getElementById('rollBtn').disabled = false; | |
| updateDiceDisplay(1); | |
| // Hapus pion lama dan pasang ulang | |
| document.querySelectorAll('.pawn').forEach(p => { | |
| p.classList.remove('winner', 'active', 'hopping', 'snake-bite', 'ladder-climb'); | |
| }); | |
| state.positions.forEach((pos, idx) => { | |
| const pawn = document.getElementById('pawn-' + idx); | |
| cells[pos].appendChild(pawn); | |
| }); | |
| updatePlayerInfo(); | |
| setActivePlayerHighlight(); | |
| } | |
| function celebrate() { | |
| const colors = ['#e74c3c', '#f39c12', '#2ecc71', '#3498db', '#9b59b6', '#ffd700']; | |
| for (let i = 0; i < 60; i++) { | |
| const confetti = document.createElement('div'); | |
| confetti.style.position = 'fixed'; | |
| confetti.style.top = '-10px'; | |
| confetti.style.left = Math.random() * window.innerWidth + 'px'; | |
| confetti.style.width = (Math.random() * 8 + 6) + 'px'; | |
| confetti.style.height = confetti.style.width; | |
| confetti.style.background = colors[Math.floor(Math.random() * colors.length)]; | |
| confetti.style.borderRadius = Math.random() > 0.5 ? '50%' : '0'; | |
| confetti.style.zIndex = '9999'; | |
| confetti.style.pointerEvents = 'none'; | |
| confetti.style.transition = 'all 2.5s ease-out'; | |
| document.body.appendChild(confetti); | |
| setTimeout(() => { | |
| confetti.style.top = window.innerHeight + 'px'; | |
| confetti.style.transform = `rotate(${Math.random() * 720}deg)`; | |
| confetti.style.opacity = '0'; | |
| }, 50); | |
| setTimeout(() => confetti.remove(), 3000); | |
| } | |
| } | |
| // === Event Listeners === | |
| document.getElementById('rollBtn').addEventListener('click', rollDice); | |
| document.getElementById('resetBtn').addEventListener('click', resetGame); | |
| document.addEventListener('keydown', (e) => { | |
| if (e.code === 'Space') { | |
| e.preventDefault(); | |
| if (!document.getElementById('rollBtn').disabled && !state.animating) { | |
| rollDice(); | |
| } | |
| } | |
| }); | |
| // === Inisialisasi === | |
| buildBoard(); | |
| updatePlayerInfo(); | |
| updateDiceDisplay(1); | |
| setActivePlayerHighlight(); | |
| </script> | |
| </body> | |
| </html> | |
Xet Storage Details
- Size:
- 22.8 kB
- Xet hash:
- e1a643cf492d034260b913b94f85d5541ec736b965c5bc999a782651bfd017a6
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.