Setelah kemarin kita sukses bikin game Ular, saatnya naik level! Proyek apa yang lebih klasik dan lebih menantang dari Tetris? Game legendaris ini adalah salah satu "ujian" yang cocok untuk melatih logika array dan manipulasi data.
Sama seperti game Ular, kita nggak akan pakai engine atau library. Kita akan bangun dari nol pakai tiga senjata andalan kita: HTML, CSS, dan JavaScript murni.
Siap? Kodenya bakal jauh lebih kompleks dari Ular, jadi siapkan kopi dan mari kita bedah!
Langkah 1: Canvas & Info (
index.html)
Pertama, kita siapkan "panggung"-nya. Mirip dengan game Ular, kita butuh <canvas> sebagai layar game. Tapi kali ini, kita tambahkan satu area lagi di sampingnya untuk nampilin skor dan tombol "Mulai".
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Game Tetris</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Game Tetris</h1>
<div id="game-container">
<!-- Layar game kita -->
<canvas id="tetrisCanvas" width="300" height="600"></canvas>
<!-- Info game di samping -->
<div id="game-info">
<b>SKOR</b>
<div id="score">0</div>
<button id="startButton">Mulai Game</button>
<p>Panah: Gerak/Rotasi</p>
<p>Bawah: Percepat</p>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
Langkah 2: Tampilan CSS (
style.css)
Sekarang, kita percantik tampilannya. Kita akan pakai flexbox untuk menata canvas dan #game-info agar tampil rapi bersebelahan. Sisanya cuma dandanin biar kelihatan kayak game (tema gelap, border keren, dll).
body {
background-color: #282c34;
color: #ffffff;
font-family: 'Arial', sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
}
h1 {
color: #61dafb;
margin-bottom: 20px;
}
#game-container {
display: flex;
flex-direction: row;
align-items: flex-start;
}
canvas {
background-color: #000000;
border: 5px solid #61dafb;
display: block;
}
#game-info {
margin-left: 20px;
min-width: 150px;
background-color: #20232a;
padding: 20px;
border-radius: 8px;
}
#game-info b {
margin-top: 0;
color: #61dafb;
text-align: center;
}
#score {
font-size: 2.5em;
font-weight: bold;
color: #ffffff;
text-align: center;
margin-bottom: 20px;
}
#startButton {
background-color: #21a1f1;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
font-size: 1.1em;
cursor: pointer;
transition: background-color 0.3s ease;
width: 100%;
}
#startButton:hover {
background-color: #1a7bbd;
}
#game-info p {
font-size: 0.9em;
color: #ccc;
text-align: center;
margin-top: 20px;
}
Langkah 3: Otak Game (
script.js)
Oke, ini adalah bagian terbesar dan paling rumit. Kodenya jauh lebih panjang dari game Ular, jadi saya akan jelaskan konsep-konsep utamanya dulu sebelum kita lihat kode lengkapnya.
Konsep Inti Logika TetrisGimana cara kerja Tetris? Kuncinya ada di dua "papan" yang berbeda:
arena(Arena): Ini adalah "papan" utama (grid 10x20). Isinya adalah semua balok yang sudah mendarat dan nggak bisa gerak lagi. Bayangin ini kayak "cap" permanen di papan.player(Pemain): Ini adalah objek yang nyimpen data balok yang sedang jatuh. Objek ini punya bentuk baloknya (misal, bentuk 'L') dan posisinya saat ini (xdany).
Game ini bekerja dengan cara:
draw(): Setiap frame, kita bersihkan kanvas, gambar semua balok diarena, lalu gambar balokplayerdi atasnya.playerDrop(): Setiap detik (atau pas tombol bawah ditekan), kita gerakinplayerke bawah satu kotak.checkCollision(): Ini fungsi paling penting. Setiapplayermau gerak (kiri, kanan, bawah, atau mutar), fungsi ini ngecek: "Kalau aku pindah ke sana, apa aku bakal nabrak dinding ATAU nabrak balok yang udah ada diarena?"- Mendarat: Kalau
playerDrop()ngecek dan ternyata "nabrak" di bawah, itu artinya baloknya mendarat. mergeToArena(): Pas mendarat, kita "cap" bentuk balokplayerke dalamarena. Balokplayersekarang jadi bagian permanen dariarena.sweepArena(): Setelah "nge-cap", kita cekarena. Ada baris yang penuh nggak? Kalau ada, hapus baris itu, kasih skor, dan turunin semua baris di atasnya.resetPlayer(): Setelah itu, kita bikin balokplayerbaru di paling atas. Ulangi terus!- Rotasi: Ini bagian paling "matematis". Kita memutar bentuk balok (yang disimpan sebagai array 2D) pakai trik "transpose + reverse".
script.js Lengkap
Ini dia kode lengkapnya. Saya sudah tambahkan banyak komentar di dalam kode untuk membantu menjelaskan setiap langkahnya.
// --- Inisialisasi Elemen ---
const canvas = document.getElementById('tetrisCanvas');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
const startButton = document.getElementById('startButton');
// --- Pengaturan Game ---
const COLS = 10; // 10 kolom
const ROWS = 20; // 20 baris
const GRID_SIZE = 30; // Ukuran setiap kotak (30px)
const EMPTY_COLOR = '#000';
// Atur ukuran kanvas sesuai grid
canvas.width = COLS * GRID_SIZE;
canvas.height = ROWS * GRID_SIZE;
// --- Bentuk-Bentuk Balok (Tetrominoes) ---
const SHAPES = [
// I
{ shape: [[1, 1, 1, 1]], color: 'cyan' },
// J
{ shape: [[1, 0, 0], [1, 1, 1]], color: 'blue' },
// L
{ shape: [[0, 0, 1], [1, 1, 1]], color: 'orange' },
// O
{ shape: [[1, 1], [1, 1]], color: 'yellow' },
// S
{ shape: [[0, 1, 1], [1, 1, 0]], color: 'green' },
// T
{ shape: [[0, 1, 0], [1, 1, 1]], color: 'purple' },
// Z
{ shape: [[1, 1, 0], [0, 1, 1]], color: 'red' }
];
// --- Variabel Game ---
let arena = []; // 'Arena' adalah grid yang menyimpan balok yang sudah mendarat
let player = {}; // Balok yang sedang dikontrol pemain
let score = 0;
let gameOver = true;
let gameInterval; // Untuk game loop (auto-drop)
// === FUNGSI INTI ===
/**
* Membuat arena (grid) baru yang kosong.
*/
function createArena(width, height) {
const matrix = [];
while (height--) {
matrix.push(new Array(width).fill(0)); // 0 = kotak kosong
}
return matrix;
}
/**
* Mendapatkan balok baru secara acak.
*/
function resetPlayer() {
const randomIdx = Math.floor(Math.random() * SHAPES.length);
const piece = SHAPES[randomIdx];
player = {
shape: piece.shape,
color: piece.color,
pos: {
x: Math.floor(COLS / 2) - Math.floor(piece.shape[0].length / 2), // Mulai di tengah
y: 0 // Mulai di atas
}
};
// Cek Game Over: Jika balok baru langsung bertabrakan, game selesai.
if (checkCollision(arena, player)) {
gameOver = true;
clearInterval(gameInterval);
alert('GAME OVER! Skor Anda: ' + score);
}
}
/**
* Memeriksa tabrakan antara balok pemain dan arena.
* @returns {boolean} True jika ada tabrakan.
*/
function checkCollision(arena, player) {
for (let y = 0; y < player.shape.length; y++) {
for (let x = 0; x < player.shape[y].length; x++) {
// Cek jika ini adalah bagian dari balok (bukan kotak kosong di matriks balok)
if (player.shape[y][x] !== 0) {
let newX = player.pos.x + x;
let newY = player.pos.y + y;
// Cek 1: Tabrakan dengan dinding (kiri/kanan/bawah)
if (newX < 0 || newX >= COLS || newY >= ROWS) {
return true;
}
// Cek 2: Tabrakan dengan balok lain yang sudah ada di arena
// Pastikan arena[newY] ada (tidak di atas layar)
if (arena[newY] && arena[newY][newX] !== 0) {
return true;
}
}
}
}
return false; // Tidak ada tabrakan
}
/**
* "Mencap" atau menggabungkan balok pemain ke arena setelah mendarat.
*/
function mergeToArena(arena, player) {
for (let y = 0; y < player.shape.length; y++) {
for (let x = 0; x < player.shape[y].length; x++) {
if (player.shape[y][x] !== 0) {
// Set warna balok ke grid arena
arena[player.pos.y + y][player.pos.x + x] = player.color;
}
}
}
}
/**
* Memeriksa dan membersihkan baris yang penuh.
*/
function sweepArena() {
let rowsCleared = 0;
// Iterasi dari bawah ke atas
for (let y = arena.length - 1; y >= 0; y--) {
// Cek apakah satu baris penuh (tidak ada '0')
if (arena[y].every(value => value !== 0)) {
// Hapus baris ini
const removedRow = arena.splice(y, 1)[0];
// Tambahkan baris kosong baru di atas
const newRow = new Array(COLS).fill(0);
arena.unshift(newRow);
rowsCleared++;
// Kita perlu cek baris yang sama lagi karena baris di atasnya sudah turun
y++;
}
}
// Update Skor (contoh: 100 poin per baris)
score += rowsCleared * 100;
// Bonus untuk Tetris (4 baris)
if (rowsCleared >= 4) {
score += 400; // Bonus
}
scoreElement.innerText = score;
}
// === FUNGSI GAMBAR ===
/**
* Menggambar seluruh kanvas (arena dan balok pemain).
*/
function draw() {
// 1. Bersihkan kanvas (dengan warna latar belakang hitam)
ctx.fillStyle = EMPTY_COLOR;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 2. Gambar arena (balok yang sudah mendarat)
drawMatrix(arena, { x: 0, y: 0 });
// 3. Gambar balok pemain
drawMatrix(player.shape, player.pos, player.color);
}
/**
* Helper function untuk menggambar matriks (baik arena maupun balok).
*/
function drawMatrix(matrix, offset, specificColor = null) {
for (let y = 0; y < matrix.length; y++) {
for (let x = 0; x < matrix[y].length; x++) {
let value = matrix[y][x];
if (value !== 0) {
// Jika specificColor diberikan (untuk balok pemain), gunakan itu.
// Jika tidak (untuk arena), gunakan nilai dari matriks (yang sudah kita simpan warnanya).
ctx.fillStyle = specificColor || value;
// Gambar kotak
ctx.fillRect((offset.x + x) * GRID_SIZE,
(offset.y + y) * GRID_SIZE,
GRID_SIZE, GRID_SIZE);
// Beri garis pinggir
ctx.strokeStyle = '#222';
ctx.strokeRect((offset.x + x) * GRID_SIZE,
(offset.y + y) * GRID_SIZE,
GRID_SIZE, GRID_SIZE);
}
}
}
}
// === FUNGSI KONTROL PEMAIN ===
/**
* Menjatuhkan balok pemain satu langkah ke bawah.
*/
function playerDrop() {
player.pos.y++;
if (checkCollision(arena, player)) {
// Jika bertabrakan, kembalikan ke posisi sebelumnya
player.pos.y--;
// Gabungkan balok ke arena
mergeToArena(arena, player);
// Cek apakah ada baris yang penuh
sweepArena();
// Dapatkan balok baru
resetPlayer();
}
}
/**
* Menggerakkan pemain ke kiri atau kanan.
* @param {number} dir -1 untuk Kiri, 1 untuk Kanan.
*/
function playerMove(dir) {
player.pos.x += dir;
if (checkCollision(arena, player)) {
// Jika bertabrakan, kembalikan
player.pos.x -= dir;
}
}
/**
* Memutar balok pemain.
*/
function playerRotate() {
const originalShape = player.shape;
const rotatedShape = rotateMatrix(player.shape);
player.shape = rotatedShape;
// Cek tabrakan setelah rotasi
if (checkCollision(arena, player)) {
// Jika nabrak, kembalikan ke bentuk asli
player.shape = originalShape;
}
}
/**
* Helper function untuk memutar matriks (logika inti Tetris).
*/
function rotateMatrix(matrix) {
// 1. Transpose (ubah baris jadi kolom dan sebaliknya)
const transposed = [];
for (let y = 0; y < matrix[0].length; y++) {
transposed.push(new Array(matrix.length).fill(0));
}
for (let y = 0; y < matrix.length; y++) {
for (let x = 0; x < matrix[y].length; x++) {
transposed[x][y] = matrix[y][x];
}
}
// 2. Reverse (balik urutan setiap baris) -> ini untuk rotasi searah jarum jam
transposed.forEach(row => row.reverse());
return transposed;
}
// === KONTROL GAME ===
/**
* Game Loop Utama
*/
function gameLoop() {
if (gameOver) {
return;
}
playerDrop(); // Jatuhkan balok
draw(); // Gambar ulang
}
/**
* Memulai game baru.
*/
function startGame() {
// Reset semuanya
arena = createArena(COLS, ROWS);
score = 0;
scoreElement.innerText = score;
gameOver = false;
resetPlayer(); // Dapatkan balok pertama
// Hentikan loop lama jika ada
if (gameInterval) {
clearInterval(gameInterval);
}
// Mulai game loop (balok turun setiap 1 detik)
// Ubah angka 1000 jadi lebih kecil (misal 500) biar lebih cepat
gameInterval = setInterval(gameLoop, 1000);
draw(); // Gambar game awal
}
// --- Event Listeners ---
// Kontrol Keyboard
document.addEventListener('keydown', (event) => {
if (gameOver) return;
if (event.key === 'ArrowLeft') {
playerMove(-1); // Kiri
} else if (event.key === 'ArrowRight') {
playerMove(1); // Kanan
} else if (event.key === 'ArrowDown') {
playerDrop(); // Percepat ke bawah
} else if (event.key === 'ArrowUp') {
playerRotate(); // Putar
}
draw(); // Gambar ulang setelah setiap gerakan
});
// Tombol Mulai
startButton.addEventListener('click', startGame);
// Gambar tampilan awal
draw();
Selesai! Apa Selanjutnya?
Gimana? Lumayan bikin pusing tapi seru banget, kan? Kamu baru aja bikin salah satu game paling ikonik sepanjang masa pakai JavaScript murni. Logika yang kamu pelajari di sini (manipulasi array 2D, deteksi tabrakan, game state) itu super berharga.
Kalau kamu masih haus tantangan, ini beberapa ide "PR" buat ngembangin game ini:
- Level & Kecepatan: Bikin game makin ngebut (kurangi interval
setInterval) setiap pemain dapet 1000 poin. - "Next Piece" Preview: Buat satu kanvas kecil lagi di
#game-infountuk nunjukin balok apa yang bakal keluar selanjutnya. - High Score: Simpan skor tertinggi pakai
localStoragebiar nggak hilang pas di-refresh. - Suara: Tambahin efek suara pas balok mendarat atau pas baris hancur!
Selamat mencoba dan selamat ngoding!

Posting Komentar