Tic Tac Toe Drag & Drop: Tutorial Membuat Game Interaktif dengan Stok Terbatas
SQ akan menunjukkan langkah demi langkah cara membuat Tic Tac Toe versi drag & drop. Tutorial ini mencakup pembuatan papan 3x3, pion X & O dengan stok terbatas, maksimal 3 pion di papan, dan animasi seru saat pion dijatuhkan. Cocok untuk pemula yang ingin belajar JavaScript interaktif.
Halo teman-teman, SQ akan membagikan tutorial seru tentang cara membuat permainan Tic Tac Toe versi drag & drop. Dalam tutorial ini, SQ akan menjelaskan langkah demi langkah agar mudah dipahami, bahkan untuk pemula.
Langkah 1: Membuat Struktur HTML
SQ mulai dengan membuat kerangka HTML dasar, termasuk dan . Di sini SQ menyiapkan papan permainan 3x3, wadah untuk pion X dan O, serta area untuk menampilkan hasil pemenang.
Langkah 2: Menata Gaya dengan CSS
SQ menggunakan CSS untuk mempercantik papan dan blok pion. Misalnya, setiap sel papan diberi border dashed, pion bisa digeser dengan cursor grab, dan ada efek saat drag & drop agar lebih interaktif.
Langkah 3: Membuat Papan dengan JavaScript
SQ membuat array board untuk menyimpan status tiap sel. Fungsi createBoard() menambahkan sel ke papan dan memberi event listener drag & drop. Saat pion dijatuhkan, fungsi onDrop() akan menangani logikanya.
Langkah 4: Mengecek Pemenang
SQ menggunakan array w berisi kombinasi kemenangan. Fungsi checkWinner() memeriksa setiap kombinasi untuk menentukan siapa yang menang, lalu menampilkan hasilnya di
Langkah 5: Menambahkan Stok dan Batas Pion
SQ menambahkan fitur stok untuk pion, misal X dan O masing-masing 5. Fungsi updateStockUI() menampilkan sisa pion, dan refreshDraggable() mencegah drag jika stok habis. SQ juga menambahkan animasi saat pion dijatuhkan agar terlihat lebih menarik.
Langkah 6: Maksimal 3 Pion di Papan
SQ membatasi maksimal 3 pion untuk X dan O di papan. Jika sudah penuh, pengguna hanya bisa memindahkan pion yang ada. SQ menandai asal pion saat drag sehingga logikanya bisa memindahkan pion dengan benar.
Dengan langkah-langkah di atas, SQ berhasil membuat Tic Tac Toe drag & drop yang interaktif, dengan stok terbatas dan batas maksimal pion. Teman-teman bisa langsung mencoba dan memodifikasi sesuai kebutuhan.
<!doctype html>
<html lang="id">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Tic Tac Toe - Drag & Drop</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui;display:flex;justify-content:center;padding:20px;background:#eef3f8}
.game-wrapper{max-width:600px;width:100%;background:#fff;padding:20px;border-radius:14px;box-shadow:0 8px 30px rgba(0,0,0,0.08)}
h1{text-align:center;margin-bottom:15px}
.board{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:20px}
.cell{
aspect-ratio:1/1;
background:#f7fafc;
display:flex;align-items:center;justify-content:center;
font-size:48px;font-weight:700;
border-radius:8px;
border:2px dashed #c9d6e2;
}
.cell.drop-hover{background:#d9f1ff}
.block-container{display:flex;justify-content:space-between;margin-top:20px;gap:10px}
.block-box{flex:1;background:#f3f7fb;padding:10px;border-radius:10px;text-align:center}
.block-item{
width:60px;height:60px;
background:#fff;
border-radius:10px;
display:flex;align-items:center;justify-content:center;
font-size:36px;font-weight:700;
margin:10px auto;
cursor:grab;
border:1px solid #d0d7e0;
}
.block-item:active{cursor:grabbing}
#result{text-align:center;margin-top:10px;font-weight:600}
</style>
</head>
<body>
<div class="game-wrapper">
<h1>Tic Tac Toe Drag & Drop</h1>
<div id="board" class="board"></div>
<div id="result"></div>
<div class="block-container">
<div class="block-box" id="boxX">
<h3>Blok X</h3>
<div class="block-item" draggable="true" data-type="X">X</div>
</div>
<div class="block-box" id="boxO">
<h3>Blok O</h3>
<div class="block-item" draggable="true" data-type="O">O</div>
</div>
</div>
</div>
<script>
const boardEl = document.getElementById('board');
let board = Array(9).fill(null);
let dragged = null;
function createBoard(){
boardEl.innerHTML = '';
for(let i=0;i<9;i++){
const c = document.createElement('div');
c.className = 'cell';
c.dataset.idx = i;
c.addEventListener('dragover', e => { e.preventDefault(); c.classList.add('drop-hover'); });
c.addEventListener('dragleave', () => c.classList.remove('drop-hover'));
c.addEventListener('drop', () => onDrop(c));
boardEl.appendChild(c);
}
}
function onDrop(cell){
const idx = cell.dataset.idx;
cell.classList.remove('drop-hover');
if(board[idx] || !dragged) return;
const type = dragged.dataset.type;
board[idx] = type;
cell.textContent = type;
checkWinner();
}
document.querySelectorAll('.block-item').forEach(b=>{
b.addEventListener('dragstart', ()=> dragged = b);
b.addEventListener('dragend', ()=> dragged = null);
});
function checkWinner(){
const w = [ [0,1,2],[3,4,5],[6,7,8], [0,3,6],[1,4,7],[2,5,8], [0,4,8],[2,4,6] ];
for(const [a,b,c] of w){
if(board[a] && board[a]===board[b] && board[a]===board[c]){
document.getElementById('result').textContent = `Pemenang: ${board[a]}`;
return true;
}
}
return false;
}
createBoard();
// --- Tambahan fitur: stok balok + balok berkurang setelah dipakai + animasi ---
let stock = { X: 5, O: 5 };
updateStockUI();
function updateStockUI(){
document.querySelector('#boxX h3').textContent = `Blok X (${stock.X})`;
document.querySelector('#boxO h3').textContent = `Blok O (${stock.O})`;
}
// Hanya izinkan drag jika stok masih ada
function refreshDraggable(){
document.querySelectorAll('.block-item').forEach(b=>{
const type = b.dataset.type;
if(stock[type] > 0){ b.setAttribute('draggable','true'); b.style.opacity = '1'; }
else{ b.removeAttribute('draggable'); b.style.opacity = '0.3'; }
});
}
refreshDraggable();
// Override onDrop agar stok berkurang
function onDrop(cell){
const idx = cell.dataset.idx;
cell.classList.remove('drop-hover');
if(board[idx] || !dragged) return;
const type = dragged.dataset.type;
if(stock[type] <= 0) return;
// Kurangi stok
stock[type]--;
updateStockUI();
refreshDraggable();
// Animasi jatuh
cell.style.transform = 'scale(0.5)';
setTimeout(()=>{ cell.style.transform = 'scale(1)'; }, 120);
board[idx] = type;
cell.textContent = type;
checkWinner();
}
// --- FITUR BARU: Maksimal 3 X & 3 O, setelah penuh harus memindahkan pion ---
let placed = { X: 0, O: 0 };
function onDrop(cell){
const idx = cell.dataset.idx;
cell.classList.remove('drop-hover');
if(!dragged) return;
const type = dragged.dataset.type;
// Jika target berisi pion, pindahkan
if(board[idx]){
const existing = board[idx];
if(existing === type){ return; }
return;
}
// Jika pion sudah 3, hanya boleh memindahkan
if(placed[type] >= 3){
const from = dragged.dataset.from;
if(from === undefined) return;
board[from] = null;
document.querySelector(`[data-idx='${from}']`).textContent = '';
}
board[idx] = type;
placed[type] = Math.min(3, placed[type] + 1);
cell.textContent = type;
dragged.removeAttribute('data-from');
checkWinner();
}
// Tandai asal saat drag pion yang sudah di papan
boardEl.addEventListener('dragstart', e => {
if(e.target.classList.contains('cell') && e.target.textContent){
dragged = document.createElement('div');
dragged.dataset.type = e.target.textContent;
dragged.dataset.from = e.target.dataset.idx;
}
});
</script>
</body>
</html>