Классический Арканоид
by LuckyGlider65413 lines15.9 KB
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Классический Арканоид</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #1a1a2e;
font-family: 'Arial', sans-serif;
}
.game-container {
background: #16213e;
padding: 20px;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
canvas {
display: block;
margin: 0 auto;
border-radius: 8px;
background: #0f3460;
cursor: none;
}
.score-board {
display: flex;
justify-content: space-between;
align-items: center;
color: #e0e0e0;
font-size: 20px;
padding: 10px 5px 15px 5px;
}
.restart-btn {
background: #e94560;
color: white;
border: none;
padding: 8px 20px;
border-radius: 6px;
font-size: 18px;
font-weight: bold;
cursor: pointer;
transition: 0.2s;
}
.restart-btn:hover {
background: #c73652;
transform: scale(1.05);
}
.controls {
color: #a8a8b3;
font-size: 14px;
text-align: center;
padding-top: 10px;
}
</style>
</head>
<body>
<div class="game-container">
<div class="score-board">
<span>💎 <span id="scoreDisplay">0</span></span>
<span>❤️ <span id="livesDisplay">3</span></span>
<button class="restart-btn" id="restartButton">🔄 Заново</button>
</div>
<canvas id="gameCanvas" width="600" height="400"></canvas>
<div class="controls">← → или мышь для управления платформой</div>
</div>
<script>
(function(){
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreSpan = document.getElementById('scoreDisplay');
const livesSpan = document.getElementById('livesDisplay');
// --- Параметры игры ---
const BALL_SIZE = 8;
const PADDLE_WIDTH = 80;
const PADDLE_HEIGHT = 12;
const PADDLE_SPEED = 6;
const BRICK_ROWS = 6;
const BRICK_COLS = 8;
const BRICK_WIDTH = 62;
const BRICK_HEIGHT = 20;
const BRICK_PADDING = 5;
const BRICK_OFFSET_TOP = 40;
const BRICK_OFFSET_LEFT = 20;
// --- Состояние игры ---
let ball = {
x: canvas.width / 2,
y: canvas.height - 40,
radius: BALL_SIZE / 2,
dx: 0,
dy: 0,
speed: 4.5
};
let paddle = {
x: (canvas.width - PADDLE_WIDTH) / 2,
y: canvas.height - 30,
width: PADDLE_WIDTH,
height: PADDLE_HEIGHT,
dx: 0
};
let bricks = [];
let score = 0;
let lives = 3;
let gameRunning = true;
let animationFrameId = null;
let gameStarted = false;
// --- Управление ---
let keys = {
left: false,
right: false
};
// --- Инициализация кирпичей ---
function initBricks() {
const colors = ['#e94560', '#f5a623', '#f7d44a', '#4ecdc4', '#45b7d1', '#a29bfe'];
bricks = [];
for (let row = 0; row < BRICK_ROWS; row++) {
for (let col = 0; col < BRICK_COLS; col++) {
const x = BRICK_OFFSET_LEFT + col * (BRICK_WIDTH + BRICK_PADDING);
const y = BRICK_OFFSET_TOP + row * (BRICK_HEIGHT + BRICK_PADDING);
const color = colors[row % colors.length];
bricks.push({
x: x,
y: y,
width: BRICK_WIDTH,
height: BRICK_HEIGHT,
color: color,
alive: true
});
}
}
}
// --- Сброс мяча ---
function resetBall() {
ball.x = canvas.width / 2;
ball.y = canvas.height - 40;
ball.dx = 0;
ball.dy = 0;
gameStarted = false;
}
// --- Начать движение мяча ---
function startBall() {
if (gameStarted) return;
const angle = (Math.random() * 60 + 15) * Math.PI / 180;
const direction = Math.random() > 0.5 ? 1 : -1;
ball.dx = ball.speed * Math.cos(angle) * direction;
ball.dy = -ball.speed * Math.sin(angle);
gameStarted = true;
}
// --- Обработка столкновений ---
function collisionDetection() {
for (let i = 0; i < bricks.length; i++) {
const brick = bricks[i];
if (!brick.alive) continue;
const ballLeft = ball.x - ball.radius;
const ballRight = ball.x + ball.radius;
const ballTop = ball.y - ball.radius;
const ballBottom = ball.y + ball.radius;
if (ballRight > brick.x && ballLeft < brick.x + brick.width &&
ballBottom > brick.y && ballTop < brick.y + brick.height) {
brick.alive = false;
score += 10;
updateScore();
// Определение направления отскока
const overlapX = Math.min(ballRight - brick.x, brick.x + brick.width - ballLeft);
const overlapY = Math.min(ballBottom - brick.y, brick.y + brick.height - ballTop);
if (overlapX < overlapY) {
ball.dx = -ball.dx;
} else {
ball.dy = -ball.dy;
}
// Проверка победы
if (bricks.every(b => !b.alive)) {
gameRunning = false;
setTimeout(() => {
alert('🎉 Поздравляем! Вы сломали все кирпичи! 🎉');
resetGame();
}, 100);
}
return;
}
}
}
// --- Обновление ---
function update() {
if (!gameRunning || !gameStarted) return;
// Движение платформы
if (keys.left && paddle.x > 0) {
paddle.x -= PADDLE_SPEED;
}
if (keys.right && paddle.x + paddle.width < canvas.width) {
paddle.x += PADDLE_SPEED;
}
// Движение мяча
ball.x += ball.dx;
ball.y += ball.dy;
// Столкновение со стенами
if (ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
ball.dx = -ball.dx;
}
if (ball.y - ball.radius < 0) {
ball.dy = -ball.dy;
}
// Столкновение с платформой
if (ball.dy > 0 &&
ball.y + ball.radius > paddle.y &&
ball.y + ball.radius < paddle.y + paddle.height + 5 &&
ball.x > paddle.x - ball.radius &&
ball.x < paddle.x + paddle.width + ball.radius) {
// Отскок с изменением угла в зависимости от места попадания
const hitPos = (ball.x - paddle.x) / paddle.width;
const angle = (hitPos - 0.5) * Math.PI / 2.2;
const speed = Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
ball.dx = speed * Math.sin(angle);
ball.dy = -speed * Math.cos(angle);
// Дополнительное ускорение для динамики
if (speed < 6.5) {
const currentSpeed = Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
const newSpeed = Math.min(currentSpeed * 1.02, 6.5);
const ratio = newSpeed / currentSpeed;
ball.dx *= ratio;
ball.dy *= ratio;
}
}
// Потеря мяча
if (ball.y + ball.radius > canvas.height) {
lives--;
updateLives();
if (lives === 0) {
gameRunning = false;
setTimeout(() => {
alert('💔 Игра окончена! Попробуйте снова.');
resetGame();
}, 100);
} else {
resetBall();
paddle.x = (canvas.width - PADDLE_WIDTH) / 2;
}
}
collisionDetection();
}
// --- Отрисовка ---
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Кирпичи
for (const brick of bricks) {
if (!brick.alive) continue;
ctx.fillStyle = brick.color;
ctx.shadowColor = 'rgba(255,255,255,0.2)';
ctx.shadowBlur = 6;
ctx.fillRect(brick.x, brick.y, brick.width, brick.height);
ctx.shadowBlur = 0;
ctx.fillStyle = 'rgba(255,255,255,0.15)';
ctx.fillRect(brick.x + 2, brick.y + 2, brick.width - 4, 6);
}
// Платформа
ctx.shadowColor = 'rgba(255,255,255,0.2)';
ctx.shadowBlur = 10;
ctx.fillStyle = '#4ecdc4';
ctx.beginPath();
ctx.roundRect(paddle.x, paddle.y, paddle.width, paddle.height, 6);
ctx.fill();
ctx.shadowBlur = 0;
// Мяч
ctx.shadowColor = 'rgba(255,215,0,0.3)';
ctx.shadowBlur = 12;
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fillStyle = '#f7d44a';
ctx.fill();
ctx.shadowBlur = 0;
ctx.fillStyle = 'rgba(255,255,255,0.4)';
ctx.beginPath();
ctx.arc(ball.x - 2, ball.y - 3, 2.5, 0, Math.PI * 2);
ctx.fill();
}
// --- Вспомогательные функции ---
function updateScore() {
scoreSpan.textContent = score;
}
function updateLives() {
livesSpan.textContent = lives;
}
function resetGame() {
score = 0;
lives = 3;
gameRunning = true;
gameStarted = false;
updateScore();
updateLives();
initBricks();
paddle.x = (canvas.width - PADDLE_WIDTH) / 2;
resetBall();
cancelAnimationFrame(animationFrameId);
gameLoop();
}
// --- Игровой цикл ---
function gameLoop() {
update();
draw();
animationFrameId = requestAnimationFrame(gameLoop);
}
// --- Обработчики событий ---
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft' || e.key === 'Left' || e.key === 'a' || e.key === 'A') {
keys.left = true;
e.preventDefault();
} else if (e.key === 'ArrowRight' || e.key === 'Right' || e.key === 'd' || e.key === 'D') {
keys.right = true;
e.preventDefault();
} else if (e.key === ' ' || e.key === 'Space') {
e.preventDefault();
if (!gameStarted && gameRunning) {
startBall();
}
}
});
document.addEventListener('keyup', (e) => {
if (e.key === 'ArrowLeft' || e.key === 'Left' || e.key === 'a' || e.key === 'A') {
keys.left = false;
e.preventDefault();
} else if (e.key === 'ArrowRight' || e.key === 'Right' || e.key === 'd' || e.key === 'D') {
keys.right = false;
e.preventDefault();
}
});
// Управление мышью (движение платформы)
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const mouseX = (e.clientX - rect.left) * scaleX;
const halfPaddle = paddle.width / 2;
paddle.x = Math.min(Math.max(mouseX - halfPaddle, 0), canvas.width - paddle.width);
});
// Клик для запуска мяча
canvas.addEventListener('click', () => {
if (!gameStarted && gameRunning) {
startBall();
}
});
// Перезапуск
document.getElementById('restartButton').addEventListener('click', resetGame);
// --- Полифил для roundRect ---
if (!CanvasRenderingContext2D.prototype.roundRect) {
CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
if (r > w / 2) r = w / 2;
if (r > h / 2) r = h / 2;
this.moveTo(x + r, y);
this.lineTo(x + w - r, y);
this.quadraticCurveTo(x + w, y, x + w, y + r);
this.lineTo(x + w, y + h - r);
this.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
this.lineTo(x + r, y + h);
this.quadraticCurveTo(x, y + h, x, y + h - r);
this.lineTo(x, y + r);
this.quadraticCurveTo(x, y, x + r, y);
this.closePath();
return this;
};
}
// --- Запуск ---
initBricks();
resetBall();
gameLoop();
})();
</script>
</body>
</html>Game Source: Классический Арканоид
Creator: LuckyGlider65
Libraries: none
Complexity: complex (413 lines, 15.9 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-luckyglider65" to link back to the original. Then publish at arcadelab.ai/publish.