Meta Description" name="description" />
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Boo Shots Game</title>
<style>
body {
margin: 0;
background-color: #1a1a1a;
color: white;
font-family: 'Courier New', Courier, monospace;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
overflow: hidden;
user-select: none;
}
#gameContainer {
position: relative;
width: 100%;
max-width: 450px;
height: 100%;
max-height: 800px;
background: #111;
border: 4px solid #fff;
box-sizing: border-box;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
#overlay {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.85);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
z-index: 10;
}
button {
background: #fff;
color: #000;
border: none;
padding: 15px 30px;
font-size: 1.2rem;
font-weight: bold;
cursor: pointer;
margin-top: 20px;
font-family: inherit;
}
button:hover { background: #ccc; }
</style>
</head>
<body>
<div id="gameContainer">
<canvas id="gameCanvas"></canvas>
<div id="overlay">
<h1 id="titleText">BOO SHOTS</h1>
<p id="descText">Tembak hantu sebelum mereka mencapai kotaknya!</p>
<button id="startBtn" onclick="startGame()">MAIN</button>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const overlay = document.getElementById('overlay');
const startBtn = document.getElementById('startBtn');
const titleText = document.getElementById('titleText');
const descText = document.getElementById('descText');
// Set resolusi internal canvas
canvas.width = 450;
canvas.height = 800;
// Konfigurasi Game (Sesuai sketsa)
const LANES = 5;
const laneWidth = canvas.width / LANES;
const bottomBlockY = canvas.height - 100; // Posisi blok bawah
const blockHeight = 80;
let playerLane = 2; // Mulai dari tengah (0, 1, 2, 3, 4)
let lives = 5;
let score = 0;
let ghosts = [];
let bullets = [];
let gameActive = false;
let spawnRate = 1500; // ms
let lastSpawn = 0;
let ghostSpeed = 2;
// Input Handler (Keyboard)
window.addEventListener('keydown', (e) => {
if (!gameActive) return;
if (e.key === 'ArrowLeft' && playerLane > 0) {
playerLane--;
} else if (e.key === 'ArrowRight' && playerLane < LANES - 1) {
playerLane++;
} else if (e.key === ' ' || e.key === 'Spacebar') {
shoot();
}
});
// Input Handler (Touch/Mouse untuk Mobile sesuai sketsa)
canvas.addEventListener('click', (e) => {
if (!gameActive) return;
const rect = canvas.getBoundingClientRect();
const clickX = ((e.clientX - rect.left) / rect.width) * canvas.width;
const clickY = ((e.clientY - rect.top) / rect.height) * canvas.height;
// Jika klik di area bawah (zona blok player)
if (clickY > bottomBlockY - 20) {
const clickedLane = Math.floor(clickX / laneWidth);
if (clickedLane >= 0 && clickedLane < LANES) {
playerLane = clickedLane;
shoot(); // Pindah langsung tembak
}
}
});
function shoot() {
bullets.push({
x: playerLane * laneWidth + laneWidth / 2,
y: bottomBlockY,
speed: 8
});
}
function spawnGhost() {
const randomLane = Math.floor(Math.random() * LANES);
// 2 tipe hantu (tengkorak dan hantu biasa seperti di gambar)
const type = Math.random() > 0.5 ? 'skull' : 'ghost';
ghosts.push({
lane: randomLane,
x: randomLane * laneWidth + laneWidth / 2,
y: 50,
type: type,
radius: 20
});
}
function startGame() {
lives = 5;
score = 0;
ghosts = [];
bullets = [];
ghostSpeed = 2;
spawnRate = 1500;
gameActive = true;
overlay.style.display = 'none';
lastSpawn = Date.now();
animate();
}
function gameOver() {
gameActive = false;
overlay.style.display = 'flex';
titleText.innerText = "GAME OVER";
descText.innerText = `Kamu berhasil menembak ${score} hantu!`;
startBtn.innerText = "MAIN LAGI";
}
// Menggambar Hantu Bergaya Sketsa
function drawGhost(ctx, x, y, type) {
ctx.strokeStyle = '#fff';
ctx.lineWidth = 3;
ctx.fillStyle = '#111';
if (type === 'ghost') {
// Hantu Klasik Klasik
ctx.beginPath();
ctx.arc(x, y, 20, Math.PI, 0, false);
ctx.lineTo(x + 20, y + 25);
// Gerigi bawah
ctx.lineTo(x + 10, y + 15);
ctx.lineTo(x, y + 25);
ctx.lineTo(x - 10, y + 15);
ctx.lineTo(x - 20, y + 25);
ctx.closePath();
ctx.fill();
ctx.stroke();
// Mata
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(x - 7, y - 2, 4, 0, Math.PI * 2);
ctx.arc(x + 7, y - 2, 4, 0, Math.PI * 2);
ctx.fill();
} else {
// Hantu Tengkorak
ctx.beginPath();
ctx.arc(x, y - 5, 22, Math.PI * 1.2, Math.PI * 1.8, false);
ctx.lineTo(x + 12, y + 15);
ctx.lineTo(x - 12, y + 15);
ctx.closePath();
ctx.fill();
ctx.stroke();
// Mata Tengkorak (Hitam/Kosong)
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(x - 8, y - 3, 6, 0, Math.PI * 2);
ctx.arc(x + 8, y - 3, 6, 0, Math.PI * 2);
ctx.fill();
// Hidung
ctx.beginPath();
ctx.moveTo(x, y + 3);
ctx.lineTo(x - 3, y + 8);
ctx.lineTo(x + 3, y + 8);
ctx.closePath();
ctx.fill();
}
}
function animate() {
if (!gameActive) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 1. Gambar Jalur/Lintasan dan Kotak Atas & Bawah
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
ctx.lineWidth = 2;
for (let i = 0; i <= LANES; i++) {
ctx.beginPath();
ctx.moveTo(i * laneWidth, 0);
ctx.lineTo(i * laneWidth, canvas.height);
ctx.stroke();
}
// Gambar Blok Kotak Atas dan Bawah (Sesuai Sketsa)
for (let i = 0; i < LANES; i++) {
ctx.strokeStyle = '#fff';
ctx.lineWidth = 3;
// Kotak Atas
ctx.strokeRect(i * laneWidth + 5, 10, laneWidth - 10, blockHeight);
// Kotak Bawah
ctx.strokeRect(i * laneWidth + 5, bottomBlockY + 10, laneWidth - 10, blockHeight);
}
// 2. Spawn Hantu otomatis
let now = Date.now();
if (now - lastSpawn > spawnRate) {
spawnGhost();
lastSpawn = now;
// Tingkatkan kesulitan perlahan
if (spawnRate > 600) spawnRate -= 20;
ghostSpeed += 0.05;
}
// 3. Update & Gambar Peluru
ctx.fillStyle = '#ff3333';
for (let i = bullets.length - 1; i >= 0; i--) {
bullets[i].y -= bullets[i].speed;
ctx.beginPath();
ctx.arc(bullets[i].x, bullets[i].y, 6, 0, Math.PI * 2);
ctx.fill();
// Hapus peluru keluar layar
if (bullets[i].y < 0) bullets.splice(i, 1);
}
// 4. Update & Gambar Hantu
for (let i = ghosts.length - 1; i >= 0; i--) {
ghosts[i].y += ghostSpeed;
drawGhost(ctx, ghosts[i].x, ghosts[i].y, ghosts[i].type);
// Cek jika hantu masuk ke kotak bawah (Lose kondisi di sketsa)
if (ghosts[i].y >= bottomBlockY + 10) {
ghosts.splice(i, 1);
lives--;
if (lives <= 0) {
gameOver();
return;
}
continue;
}
// Cek tabrakan dengan peluru
for (let j = bullets.length - 1; j >= 0; j--) {
// Cek jalurnya sama dan jarak vertikal dekat
let distX = Math.abs(bullets[j].x - ghosts[i].x);
let distY = Math.abs(bullets[j].y - ghosts[i].y);
if (distX < laneWidth / 2 && distY < 25) {
ghosts.splice(i, 1);
bullets.splice(j, 1);
score += 10;
break;
}
}
}
// 5. Gambar Karakter Player (Sesuai ikon tangan/pusaran di sketsa)
const pX = playerLane * laneWidth + laneWidth / 2;
const pY = bottomBlockY + 50;
// Menggambar simbol pusaran/penembak
ctx.strokeStyle = '#00ffff';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(pX, pY, 20, 0, Math.PI * 2);
ctx.stroke();
// Moncong tembakan ke atas
ctx.fillStyle = '#00ffff';
ctx.fillRect(pX - 5, pY - 30, 10, 15);
// 6. Tampilkan Score dan Nyawa (UI)
ctx.fillStyle = '#fff';
ctx.font = '20px "Courier New"';
ctx.fillText(`SKOR: ${score}`, 20, 120);
// Gambar Jantung/Nyawa (HAVE 5 LIFE di sketsa)
let hearts = "";
for(let h=0; h<lives; h++) hearts += "♥ ";
ctx.fillStyle = '#ff3366';
ctx.fillText(`NYAWA: ${hearts}`, 20, 150);
requestAnimationFrame(animate);
}
</script>
</body>
</html>
1
1
10KB
10KB
89.0ms
116.0ms
89.0ms