Files
gamercomp/games/samples/brick-breaker.html
2026-04-30 02:14:25 +00:00

427 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Brick Breaker</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 100%; height: 100%; overflow: hidden;
background: #1a1a2e; touch-action: none;
}
canvas { display: block; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
// Audio Context
const AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx = null;
let musicEnabled = true;
let sfxEnabled = true;
let musicPlaying = false;
function initAudio() {
if (!audioCtx) audioCtx = new AudioContext();
if (audioCtx.state === 'suspended') audioCtx.resume();
}
// Catchy melody - Super Mario style bouncy tune
const melodyNotes = [
{n: 659, d: 0.15}, {n: 659, d: 0.15}, {n: 0, d: 0.15}, {n: 659, d: 0.15},
{n: 0, d: 0.15}, {n: 523, d: 0.15}, {n: 659, d: 0.3}, {n: 784, d: 0.3},
{n: 0, d: 0.3}, {n: 392, d: 0.3}, {n: 0, d: 0.3},
{n: 523, d: 0.2}, {n: 0, d: 0.1}, {n: 392, d: 0.2}, {n: 0, d: 0.1}, {n: 330, d: 0.2},
{n: 0, d: 0.1}, {n: 440, d: 0.2}, {n: 494, d: 0.2}, {n: 466, d: 0.1}, {n: 440, d: 0.2},
{n: 392, d: 0.2}, {n: 659, d: 0.2}, {n: 784, d: 0.2}, {n: 880, d: 0.2},
{n: 698, d: 0.15}, {n: 784, d: 0.15}, {n: 0, d: 0.15}, {n: 659, d: 0.2},
{n: 523, d: 0.15}, {n: 587, d: 0.15}, {n: 494, d: 0.2}, {n: 0, d: 0.2}
];
const bassNotes = [
{n: 131, d: 0.3}, {n: 165, d: 0.3}, {n: 196, d: 0.3}, {n: 165, d: 0.3},
{n: 131, d: 0.3}, {n: 165, d: 0.3}, {n: 196, d: 0.3}, {n: 247, d: 0.3},
{n: 175, d: 0.3}, {n: 220, d: 0.3}, {n: 262, d: 0.3}, {n: 220, d: 0.3},
{n: 196, d: 0.3}, {n: 247, d: 0.3}, {n: 294, d: 0.3}, {n: 247, d: 0.3}
];
let melodyIndex = 0, bassIndex = 0;
let nextMelodyTime = 0, nextBassTime = 0;
function playTone(freq, duration, type = 'square', volume = 0.15) {
if (!audioCtx || !musicEnabled || freq === 0) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.value = freq;
gain.gain.setValueAtTime(volume, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration * 0.9);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + duration);
}
function playSfx(type) {
if (!audioCtx || !sfxEnabled) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
if (type === 'hit') {
osc.frequency.setValueAtTime(520, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(320, audioCtx.currentTime + 0.1);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
osc.type = 'square';
} else if (type === 'break') {
osc.frequency.setValueAtTime(800, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(200, audioCtx.currentTime + 0.15);
gain.gain.setValueAtTime(0.25, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.15);
osc.type = 'sawtooth';
} else if (type === 'die') {
osc.frequency.setValueAtTime(400, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(100, audioCtx.currentTime + 0.4);
gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.4);
osc.type = 'sawtooth';
} else if (type === 'win') {
osc.frequency.setValueAtTime(523, audioCtx.currentTime);
osc.frequency.setValueAtTime(659, audioCtx.currentTime + 0.1);
osc.frequency.setValueAtTime(784, audioCtx.currentTime + 0.2);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime + 0.2);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.4);
osc.type = 'square';
}
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.5);
}
function updateMusic() {
if (!audioCtx || !musicEnabled || !musicPlaying) return;
const now = audioCtx.currentTime;
if (now >= nextMelodyTime) {
const note = melodyNotes[melodyIndex];
playTone(note.n, note.d, 'square', 0.12);
nextMelodyTime = now + note.d;
melodyIndex = (melodyIndex + 1) % melodyNotes.length;
}
if (now >= nextBassTime) {
const note = bassNotes[bassIndex];
playTone(note.n, note.d, 'triangle', 0.2);
nextBassTime = now + note.d;
bassIndex = (bassIndex + 1) % bassNotes.length;
}
}
function startMusic() {
if (!musicPlaying && musicEnabled) {
initAudio();
musicPlaying = true;
nextMelodyTime = audioCtx.currentTime;
nextBassTime = audioCtx.currentTime;
}
}
// Game setup
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
let W, H, safeTop, safeBottom, gameH, gameTop;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
gameH = safeBottom - safeTop;
gameTop = safeTop;
}
resize();
window.addEventListener('resize', resize);
// Game state
let gameRunning = false;
let gameOver = false;
let score = 0;
let lives = 5;
let level = 1;
let ballSpeed = 3;
// Paddle
const paddle = { w: 120, h: 15 };
paddle.x = W / 2 - paddle.w / 2;
paddle.y = safeBottom - 50;
// Ball
const ball = { r: 10, x: W / 2, y: safeBottom - 100, dx: 0, dy: 0 };
// Bricks
let bricks = [];
const brickRows = 5;
const brickCols = 8;
const brickH = 25;
const brickPadding = 6;
const colors = ['#ff6b6b', '#feca57', '#48dbfb', '#ff9ff3', '#54a0ff'];
function initBricks() {
bricks = [];
const brickW = (W - brickPadding * (brickCols + 1)) / brickCols;
for (let r = 0; r < brickRows; r++) {
for (let c = 0; c < brickCols; c++) {
bricks.push({
x: c * (brickW + brickPadding) + brickPadding,
y: gameTop + 40 + r * (brickH + brickPadding),
w: brickW,
h: brickH,
color: colors[r],
alive: true
});
}
}
}
function resetBall() {
ball.x = paddle.x + paddle.w / 2;
ball.y = paddle.y - ball.r - 5;
const angle = -Math.PI / 2 + (Math.random() - 0.5) * Math.PI / 3;
ball.dx = Math.cos(angle) * ballSpeed;
ball.dy = Math.sin(angle) * ballSpeed;
}
function init() {
score = 0;
lives = 5;
level = 1;
ballSpeed = 3; // Start very slow
paddle.x = W / 2 - paddle.w / 2;
paddle.y = safeBottom - 50;
initBricks();
resetBall();
gameOver = false;
gameRunning = true;
startMusic();
}
// Controls
let targetX = W / 2;
let keys = { left: false, right: false };
document.addEventListener('keydown', e => {
if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = true;
if (e.key === 'ArrowRight' || e.key === 'd') keys.right = true;
if (e.key === ' ' || e.key === 'Enter') {
if (gameOver) init();
}
initAudio();
});
document.addEventListener('keyup', e => {
if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = false;
if (e.key === 'ArrowRight' || e.key === 'd') keys.right = false;
});
canvas.addEventListener('mousemove', e => {
targetX = e.clientX;
});
canvas.addEventListener('click', e => {
initAudio();
if (gameOver) init();
});
canvas.addEventListener('touchstart', e => {
e.preventDefault();
initAudio();
if (gameOver) init();
targetX = e.touches[0].clientX;
});
canvas.addEventListener('touchmove', e => {
e.preventDefault();
targetX = e.touches[0].clientX;
});
function update() {
if (!gameRunning || gameOver) return;
// Paddle movement
if (keys.left) paddle.x -= 10;
if (keys.right) paddle.x += 10;
// Mouse/touch follow
const diff = targetX - (paddle.x + paddle.w / 2);
paddle.x += diff * 0.15;
// Bounds
if (paddle.x < 0) paddle.x = 0;
if (paddle.x + paddle.w > W) paddle.x = W - paddle.w;
// Ball movement
ball.x += ball.dx;
ball.y += ball.dy;
// Wall collision
if (ball.x - ball.r < 0 || ball.x + ball.r > W) {
ball.dx = -ball.dx;
ball.x = Math.max(ball.r, Math.min(W - ball.r, ball.x));
}
if (ball.y - ball.r < gameTop) {
ball.dy = -ball.dy;
ball.y = gameTop + ball.r;
}
// Paddle collision (generous hitbox)
if (ball.dy > 0 &&
ball.y + ball.r >= paddle.y - 5 &&
ball.y < paddle.y + paddle.h &&
ball.x >= paddle.x - 15 &&
ball.x <= paddle.x + paddle.w + 15) {
ball.dy = -Math.abs(ball.dy);
const hitPos = (ball.x - paddle.x) / paddle.w;
ball.dx = (hitPos - 0.5) * ballSpeed * 2;
ball.y = paddle.y - ball.r - 1;
playSfx('hit');
}
// Brick collision
for (let brick of bricks) {
if (!brick.alive) continue;
if (ball.x + ball.r > brick.x - 5 &&
ball.x - ball.r < brick.x + brick.w + 5 &&
ball.y + ball.r > brick.y - 5 &&
ball.y - ball.r < brick.y + brick.h + 5) {
brick.alive = false;
ball.dy = -ball.dy;
score += 10;
playSfx('break');
// Check level complete
if (bricks.every(b => !b.alive)) {
level++;
ballSpeed = Math.min(8, 3 + level * 0.5); // Gradual speed increase
initBricks();
resetBall();
playSfx('win');
}
break;
}
}
// Ball lost
if (ball.y > H) {
lives--;
playSfx('die');
if (lives <= 0) {
gameOver = true;
gameRunning = false;
} else {
resetBall();
}
}
updateMusic();
}
function draw() {
// Background
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, W, H);
// Safe zone indicators (subtle)
ctx.fillStyle = '#16213e';
ctx.fillRect(0, 0, W, safeTop);
ctx.fillRect(0, safeBottom, W, H - safeBottom);
// Score and lives
ctx.fillStyle = '#fff';
ctx.font = 'bold 20px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Score: ${score}`, 15, 30);
ctx.textAlign = 'right';
ctx.fillText(`Lives: ${'❤️'.repeat(lives)}`, W - 15, 30);
ctx.textAlign = 'center';
ctx.fillText(`Level ${level}`, W / 2, 30);
// Bricks
for (let brick of bricks) {
if (!brick.alive) continue;
ctx.fillStyle = brick.color;
ctx.beginPath();
ctx.roundRect(brick.x, brick.y, brick.w, brick.h, 5);
ctx.fill();
}
// Paddle
ctx.fillStyle = '#00ffff';
ctx.shadowColor = '#00ffff';
ctx.shadowBlur = 15;
ctx.beginPath();
ctx.roundRect(paddle.x, paddle.y, paddle.w, paddle.h, 8);
ctx.fill();
ctx.shadowBlur = 0;
// Ball
ctx.fillStyle = '#fff';
ctx.shadowColor = '#fff';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
// Game over screen
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.8)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#fff';
ctx.font = 'bold 36px Arial';
ctx.textAlign = 'center';
ctx.fillText('GAME OVER', W / 2, H / 2 - 40);
ctx.font = '24px Arial';
ctx.fillText(`Final Score: ${score}`, W / 2, H / 2 + 10);
// Play again button
ctx.fillStyle = '#00ffff';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 40, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#000';
ctx.font = 'bold 20px Arial';
ctx.fillText('TAP TO PLAY AGAIN', W / 2, H / 2 + 72);
}
}
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
// Window interface
window.gameScore = 0;
Object.defineProperty(window, 'gameScore', { get: () => score });
window.pauseGame = () => { gameRunning = false; musicPlaying = false; };
window.resumeGame = () => { if (!gameOver) { gameRunning = true; startMusic(); } };
window.setMusicEnabled = (v) => { musicEnabled = v; if (!v) musicPlaying = false; };
window.setSfxEnabled = (v) => { sfxEnabled = v; };
// Start
init();
gameLoop();
</script>
</body>
</html>