Langitzt/idet / ular-tangga.html
Langitzt's picture
download
raw
22.8 kB
<!DOCTYPE html>
<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 !important;
box-shadow: inset 0 0 15px rgba(255, 193, 7, 0.8);
transform: scale(1.08);
z-index: 5;
}
.cell.snake-target {
background: #ff8a80 !important;
box-shadow: inset 0 0 15px rgba(244, 67, 54, 0.6);
}
.cell.ladder-target {
background: #80d8ff !important;
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.