Skip to content

Instantly share code, notes, and snippets.

@andrejsharapov
Last active April 28, 2025 08:43
Show Gist options
  • Select an option

  • Save andrejsharapov/8e88dccd2960b117689f797b3af268e4 to your computer and use it in GitHub Desktop.

Select an option

Save andrejsharapov/8e88dccd2960b117689f797b3af268e4 to your computer and use it in GitHub Desktop.
Spy Maze Game
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Шпионский лабиринт v1.1.0</title>
<style>
body {
background-color: #222;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
flex-direction: column;
color: white;
font-family: sans-serif;
}
#gameCanvas {
display: none;
}
#hud {
color: white;
font-family: sans-serif;
position: absolute;
top: 10px;
right: 10px;
left: 10px;
font-size: 20px;
display: none;
grid-template-columns: repeat(3, 1fr);
place-items: center;
text-align: center;
}
#controls {
margin-top: 10px;
display: flex;
gap: 10px;
display: none;
}
button {
background-color: #4CAF50;
border: none;
color: white;
padding: 10px 20px;
cursor: pointer;
font-size: 16px;
}
#levelCompleteScreen,
#gameOverScreen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: none;
justify-content: center;
align-items: center;
flex-direction: column;
color: white;
}
#levelCompleteScreen button,
#gameOverScreen button {
margin-top: 20px;
}
#pausePanel {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.8);
color: white;
padding: 20px;
border-radius: 10px;
font-size: 24px;
display: none;
}
#startScreen {
text-align: center;
}
#startScreen h1 {
font-size: 3em;
margin-bottom: 20px;
}
#startScreen p {
font-size: 1.2em;
line-height: 1.5;
margin-bottom: 30px;
max-width: 800px;
}
</style>
</head>
<body>
<!-- Экран приветствия -->
<div id="startScreen">
<h1>Шпионский лабиринт</h1>
<p>
Добро пожаловать в Шпионский лабиринт, агент! Твоя миссия — собрать необходимые сведения и передать их в штаб. Ты
должен добраться до него незамеченным. Я отметил его синей меткой. Избегай разведчиков противника, иначе будет
худо. Добравшись до штаба ты получишь очки репутации и новое задание. Имей ввиду, каждое новое задание будет
сложнее предыдущего, ты не должен сдаваться! На каждое задание у тебя есть 5 попыток, но имей ввиду, за
каждое ранение ты теряешь 100 очков. Используй клавиши со стрелками для перемещения.
Удачи, агент!
</p>
<button id="startGameButton">Вперёд!</button>
</div>
<div id="hud">
<div>
<span id="lives">❤❤❤❤❤</span>
</div>
<div>
Время: <span id="time">00:00</span>
| Уровень: <span id="level">1</span>
<p>Собрано сведений: <span id="collectedPieces">0</span>/5</p>
</div>
<div>
🏆 <span id="score"> 1000</span>
</div>
</div>
<canvas id="gameCanvas"></canvas>
<div id="controls">
<button id="pauseButton">Пауза</button>
</div>
<div id="pausePanel">
Игра остановлена
</div>
<!-- Экран "Game Over" -->
<div id="gameOverScreen">
<h2>Ты не справился! </h2>
<p>Не отчаивайся, я уверен, у тебя всё получится в следующий раз!</p>
<button id="restartButton">Начать заново</button>
</div>
<div id="levelCompleteScreen">
<h2>Этап пройден!</h2>
<p>Готов к следующему заданию?</p>
<button id="nextLevelButton">Перейти</button>
</div>
<script>
// === 1. Настройки ===
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const canvasWidth = window.innerWidth * 0.7;
const canvasHeight = window.innerHeight * 0.7;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const mazeWidth = 40;
const mazeHeight = 20;
const cellSize = Math.min(canvasWidth / mazeWidth, canvasHeight / mazeHeight);
// === 2. Данные игры ===
const treeEmojis = ["🌲", "🌳"];
const heartSpawnChance = 0.5; // Вероятность появления сердечка (50%)
let maze = [];
let treeMap = []; // Добавляем массив для хранения выбранных деревьев
let heart = null; // Переменная для хранения координат сердечка
let paperPieces = []; // Массив для хранения координат кусочков бумаги
let collectedPieces = 0; // Количество собранных кусочков бумаги
let spy = { x: 1, y: 1, radius: cellSize / 2 - 2, color: 'blue' };
let guards = [];
let gameWon = false;
let gameOver = false;
let gamePaused = true;
let currentLevel = 1;
let maxLevels = 10;
let playerLives = 5;
let playerScore = 500;
let startTime;
let elapsedTime = 0;
let pauseStartTime = 0;
const themes = {
"forest": {
wallColor: "#228B22",
pathColor: "#DEB887",
exitColor: "#3b6ad1",
spyColor: "#006400",
guardColor: "#800000"
},
"dungeon": {
wallColor: "gray",
pathColor: "#555",
exitColor: "brown",
spyColor: "darkblue",
guardColor: "firebrick"
}
};
let currentTheme = "forest"; // Текущая тема по умолчанию
// Новые переменные для щита
let shield = null; // Координаты щита на карте (или null, если щита нет)
const shieldSpawnChance = 0.8; // Вероятность появления щита на уровне (30%)
let shieldActive = false; // Активен ли щит в данный момент
let shieldTimer = 0; // Сколько времени осталось до конца действия щита
const shieldDuration = 15; // Длительность действия щита в секундах
// === 3. Кнопки ===
const pauseButton = document.getElementById('pauseButton');
const nextLevelButton = document.getElementById('nextLevelButton');
const levelCompleteScreen = document.getElementById('levelCompleteScreen');
const pausePanel = document.getElementById('pausePanel');
const startScreen = document.getElementById('startScreen');
const startGameButton = document.getElementById('startGameButton');
const gameOverScreen = document.getElementById('gameOverScreen');
const restartButton = document.getElementById('restartButton');
// === 4. Функции интерфейса ===
function generateHearts(lives, maxLives) {
let hearts = '';
for (let i = 0; i < lives; i++) {
hearts += '❤️';
}
for (let i = 0; i < maxLives - lives; i++) {
hearts += '💔';
}
return hearts;
}
function generateHeart() {
let x, y;
do {
x = Math.floor(Math.random() * mazeWidth);
y = Math.floor(Math.random() * mazeHeight);
} while (maze[y][x] !== 0 || (x === 1 && y === 1) || (x == mazeWidth - 2 && y == mazeHeight - 2)); // Убеждаемся, что сердечко не появляется на стене или на старте или на финише
heart = { x: x, y: y };
}
function formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.floor(seconds % 60);
const formattedMinutes = String(minutes).padStart(2, '0');
const formattedSeconds = String(remainingSeconds).padStart(2, '0');
return `${formattedMinutes}:${formattedSeconds}`;
}
// === 5. Обработчики событий ===
pauseButton.addEventListener('click', togglePause);
nextLevelButton.addEventListener('click', continueToNextLevel);
startGameButton.addEventListener('click', showGame);
restartButton.addEventListener('click', restartGame);
function showGame() {
startScreen.style.display = 'none';
canvas.style.display = 'block';
hud.style.display = 'grid';
controls.style.display = 'flex';
gamePaused = false
init();
gameLoop();
startTime = Date.now();
elapsedTime = 0;
}
function togglePause() {
gamePaused = !gamePaused;
pausePanel.style.display = gamePaused ? 'block' : 'none';
if (gamePaused) {
pauseStartTime = Date.now(); // Запоминаем время постановки на паузу
} else {
startTime += (Date.now() - pauseStartTime); // Корректируем startTime
}
}
function continueToNextLevel() {
levelCompleteScreen.style.display = 'none';
levelComplete();
}
function restartGame() {
gameOverScreen.style.display = 'none';
showGame()
}
// Функция для активации щита
function generateShield() {
let x, y;
do {
x = Math.floor(Math.random() * mazeWidth);
y = Math.floor(Math.random() * mazeHeight);
} while (maze[y][x] !== 0 || (x === 1 && y === 1) || (x == mazeWidth - 2 && y == mazeHeight - 2)); // Убеждаемся, что щит не появляется на стене, старте или финише
shield = { x: x, y: y };
}
// Функции генерации лабиринта
function generateMaze() {
maze = [];
treeMap = []; // Инициализируем treeMap
for (let y = 0; y < mazeHeight; y++) {
maze[y] = [];
treeMap[y] = []; // Инициализируем treeMap[y]
for (let x = 0; x < mazeWidth; x++) {
maze[y][x] = 1; // 1 означает стену
treeMap[y][x] = treeEmojis[Math.floor(Math.random() * treeEmojis.length)]; // Выбираем случайное дерево и сохраняем в treeMap
}
}
function recursiveBacktracker(x, y) {
maze[y][x] = 0; // 0 означает проход
const directions = shuffle([
{ dx: -2, dy: 0 },
{ dx: 2, dy: 0 },
{ dx: 0, dy: -2 },
{ dx: 0, dy: 2 }
]);
for (const dir of directions) {
const newX = x + dir.dx;
const newY = y + dir.dy;
if (newX > 0 && newX < mazeWidth - 1 && newY > 0 && newY < mazeHeight - 1 && maze[newY][newX] === 1) {
maze[y + dir.dy / 2][x + dir.dx / 2] = 0;
recursiveBacktracker(newX, newY);
}
}
}
const startX = 1;
const startY = 1;
recursiveBacktracker(startX, startY);
const extraPassages = mazeWidth * mazeHeight * 0.05;
for (let i = 0; i < extraPassages; i++) {
const x = Math.floor(Math.random() * (mazeWidth - 2)) + 1;
const y = Math.floor(Math.random() * (mazeHeight - 2)) + 1;
maze[y][x] = 0;
}
return maze;
}
function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
function placeGuards() {
guards = [];
let numGuards = 1 + (currentLevel * 2);
for (let i = 0; i < numGuards; i++) {
let guardX, guardY;
do {
guardX = Math.floor(Math.random() * mazeWidth);
guardY = Math.floor(Math.random() * mazeHeight);
} while (maze[guardY][guardX] !== 0 || (guardX === 1 && guardY === 1));
let directions = ["left", "right", "up", "down"];
let randomDirection = directions[Math.floor(Math.random() * directions.length)];
guards.push({
x: guardX,
y: guardY,
radius: cellSize / 2 - 2,
color: 'red',
direction: randomDirection,
visionRadius: 5,
isChasing: false,
moveTimer: 0,
moveInterval: 550
});
}
}
// === 7. Функции отрисовки ===
function drawMaze() {
const theme = themes[currentTheme];
for (let y = 0; y < mazeHeight; y++) {
for (let x = 0; x < mazeWidth; x++) {
const tile = maze[y][x];
if (tile === 1) {
ctx.fillStyle = theme.wallColor;
ctx.font = cellSize + "px Arial";
ctx.fillText(treeMap[y][x], x * cellSize, y * cellSize + cellSize - 2); // Используем дерево из treeMap
} else if (tile === 2) {
ctx.fillStyle = theme.exitColor;
ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize)
} else {
ctx.fillStyle = theme.pathColor;
ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
}
}
// Отрисовываем доп. жизнь
if (heart) {
ctx.font = cellSize + "px Arial";
ctx.fillStyle = "red";
ctx.fillText("💖", heart.x * cellSize, heart.y * cellSize + cellSize - 2);
}
// Отрисовываем щит
if (shield) {
ctx.font = cellSize + "px Arial";
ctx.fillStyle = "blue";
ctx.fillText("🛡️", shield.x * cellSize, shield.y * cellSize + cellSize - 2);
}
// Отрисовываем кусочки бумаги
ctx.font = cellSize + "px Arial";
ctx.fillStyle = "white";
for (let i = 0; i < paperPieces.length; i++) {
const piece = paperPieces[i];
ctx.fillText("📑", piece.x * cellSize, piece.y * cellSize + cellSize - 2);
}
}
}
function drawCircle(x, y, radius, color) {
ctx.beginPath();
ctx.arc(x * cellSize + cellSize / 2, y * cellSize + cellSize / 2, radius, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
}
function drawSpy() {
const theme = themes[currentTheme];
drawCircle(spy.x, spy.y, spy.radius, theme.spyColor); // Используем цвет шпиона из текущей темы
// Визуальное отображение щита
if (shieldActive) {
ctx.beginPath();
ctx.arc(spy.x * cellSize + cellSize / 2, spy.y * cellSize + cellSize / 2, spy.radius + 5, 0, Math.PI * 2);
ctx.strokeStyle = "yellow";
ctx.lineWidth = 3;
ctx.stroke();
}
}
function drawGuards() {
const theme = themes[currentTheme];
for (let i = 0; i < guards.length; i++) {
const guard = guards[i];
drawCircle(guard.x, guard.y, guard.radius, guard.color);
//Отрисовываем радиус видимости
ctx.beginPath();
ctx.arc(guard.x * cellSize + cellSize / 2, guard.y * cellSize + cellSize / 2, guard.visionRadius * cellSize, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255, 0, 0, 0.1)'; // Цвет радиуса
ctx.fill();
// Отрисовываем линию, указывающую направление
ctx.strokeStyle = 'rgba(255, 0, 0, 0.5)'; // Цвет линии
ctx.lineWidth = 2; // Толщина линии
ctx.beginPath();
ctx.moveTo(guard.x * cellSize + cellSize / 2, guard.y * cellSize + cellSize / 2);
let lineEndX = guard.x;
let lineEndY = guard.y;
if (guard.direction === "left") lineEndX--;
else if (guard.direction === "right") lineEndX++;
else if (guard.direction === "up") lineEndY--;
else if (guard.direction === "down") lineEndY++;
ctx.lineTo(lineEndX * cellSize + cellSize / 2, lineEndY * cellSize + cellSize / 2);
ctx.stroke();
}
}
function changeTheme(themeName) {
if (themes[themeName]) {
currentTheme = themeName;
//initLevel(); // Перерисовываем лабиринт с новой темой
}
}
function drawGameOver() {
gameOverScreen.style.display = 'flex';
}
function drawWin() {
ctx.font = "40px Arial";
ctx.fillStyle = "green";
ctx.textAlign = "center";
ctx.fillText("You Win!", canvasWidth / 2, canvasHeight / 2);
}
// === 8. Логика игры ===
let nextMove = null;
document.addEventListener("keydown", function (event) {
if (event.code === "KeyE") {
if (maze[spy.y][spy.x] === 2) {
continueToNextLevel();
}
}
});
document.addEventListener('keydown', function (event) {
if (gamePaused) return;
switch (event.key) {
case 'ArrowUp': nextMove = 'up'; break;
case 'ArrowDown': nextMove = 'down'; break;
case 'ArrowLeft': nextMove = 'left'; break;
case 'ArrowRight': nextMove = 'right'; break;
case 'Enter':
if (gameOver) {
restartGame();
}
break;
}
moveSpy();
});
function canSee(guard, targetX, targetY) {
const dx = targetX - guard.x;
const dy = targetY - guard.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > guard.visionRadius) return false;
let startX = guard.x;
let startY = guard.y;
let endX = targetX;
let endY = targetY;
let x = startX;
let y = startY;
while (x !== endX || y !== endY) {
if (x < endX) x++;
else if (x > endX) x--;
if (y < endY) y++;
else if (y > endY) y--;
if (maze[y][x] === 1) {
return false;
}
}
return true;
}
function aStar(startX, startY, endX, endY, maze) {
const openSet = [{ x: startX, y: startY, g: 0, h: heuristic(startX, startY, endX, endY) }];
const closedSet = [];
function heuristic(x, y, endX, endY) {
return Math.abs(x - endX) + Math.abs(y - endY);
}
function reconstructPath(node) {
const path = [];
while (node.parent) {
path.push({ x: node.x, y: node.y });
node = node.parent;
}
return path.reverse();
}
while (openSet.length > 0) {
// Find the node with the lowest f = g + h value
let current = openSet[0];
let currentIndex = 0;
for (let i = 1; i < openSet.length; i++) {
if (openSet[i].g + openSet[i].h < current.g + current.h) {
current = openSet[i];
currentIndex = i;
}
}
// If found the goal
if (current.x === endX && current.y === endY) {
return reconstructPath(current);
}
// Remove the current node from the open set and add it to the closed set
openSet.splice(currentIndex, 1);
closedSet.push(current);
// Generate neighbors (adjacent cells)
const neighbors = [
{ x: current.x - 1, y: current.y },
{ x: current.x + 1, y: current.y },
{ x: current.x, y: current.y - 1 },
{ x: current.x, y: current.y + 1 }
];
for (const neighbor of neighbors) {
// Check if neighbor is within the maze bounds and is not a wall
if (neighbor.x >= 0 && neighbor.x < mazeWidth && neighbor.y >= 0 && neighbor.y < mazeHeight && maze[neighbor.y][neighbor.x] === 0) {
// Check if neighbor is already in the closed set
const inClosedSet = closedSet.find(node => node.x === neighbor.x && node.y === neighbor.y);
if (inClosedSet) continue;
// Calculate g, h and f values
const g = current.g + 1;
const h = heuristic(neighbor.x, neighbor.y, endX, endY);
// Check if neighbor is already in the open set
let inOpenSet = openSet.find(node => node.x === neighbor.x && node.y === neighbor.y);
if (!inOpenSet) {
// Add neighbor to the open set
neighbor.g = g;
neighbor.h = h;
neighbor.parent = current;
openSet.push(neighbor);
} else if (g < inOpenSet.g) {
// This path to neighbor is better than the previous one
inOpenSet.g = g;
inOpenSet.parent = current;
}
}
}
}
// If no path is found, return an empty array
return [];
}
function moveGuards(deltaTime) {
if (gamePaused) return;
for (let i = 0; i < guards.length; i++) {
const guard = guards[i];
guard.moveTimer += deltaTime;
if (guard.moveTimer >= guard.moveInterval) {
guard.moveTimer -= guard.moveInterval;
if (canSee(guard, spy.x, spy.y)) {
guard.isChasing = true;
// Обновляем направление движения врага при начале преследования
if (spy.x > guard.x) guard.direction = "right";
else if (spy.x < guard.x) guard.direction = "left";
else if (spy.y > guard.y) guard.direction = "down";
else if (spy.y < guard.y) guard.direction = "up";
} else {
guard.isChasing = false;
}
if (guard.isChasing) {
const path = aStar(guard.x, guard.y, spy.x, spy.y, maze);
if (path.length > 0) {
const nextX = path[0].x;
const nextY = path[0].y;
if (maze[nextY][nextX] === 0) {
guard.x = nextX;
guard.y = nextY;
if (nextX > guard.x) guard.direction = "right";
else if (nextX < guard.x) guard.direction = "left";
else if (nextY > guard.y) guard.direction = "down";
else if (nextY < guard.y) guard.direction = "up";
}
}
} else {
let dx = 0;
let dy = 0;
if (guard.direction === "left") dx = -1;
else if (guard.direction === "right") dx = 1;
else if (guard.direction === "up") dy = -1;
else if (guard.direction === "down") dy = 1;
if (guard.y + dy >= 0 && guard.y + dy < mazeHeight &&
guard.x + dx >= 0 && guard.x + dx < mazeWidth &&
maze[guard.y + dy][guard.x + dx] === 0) {
guard.x += dx;
guard.y += dy;
} else {
// Try to find a valid direction
let possibleDirections = ["left", "right", "up", "down"];
let newDirectionFound = false;
for (let attempt = 0; attempt < 4; attempt++) {
let newDirection = possibleDirections[Math.floor(Math.random() * possibleDirections.length)];
dx = 0;
dy = 0;
if (newDirection === "left") dx = -1;
else if (newDirection === "right") dx = 1;
else if (newDirection === "up") dy = -1;
else if (newDirection === "down") dy = 1;
if (guard.y + dy >= 0 && guard.y + dy < mazeHeight &&
guard.x + dx >= 0 && guard.x + dx < mazeWidth &&
maze[guard.y + dy][guard.x + dx] === 0) {
guard.direction = newDirection;
newDirectionFound = true;
guard.x += dx;
guard.y += dy;
break;
}
}
// If a valid direction cannot be found, do nothing
}
}
}
}
}
function moveSpy() {
if (gamePaused) return;
if (nextMove) {
let newX = spy.x;
let newY = spy.y;
switch (nextMove) {
case 'up': newY--; break;
case 'down': newY++; break;
case 'left': newX--; break;
case 'right': newX++; break;
}
if (maze[newY]?.[newX] === 0 || maze[newY]?.[newX] === 2) {
spy.x = newX;
spy.y = newY;
}
nextMove = null;
}
}
function checkCollisions() {
if (gamePaused) return;
for (let i = guards.length - 1; i >= 0; i--) {
const guard = guards[i];
const dx = spy.x * cellSize - guard.x * cellSize;
const dy = spy.y * cellSize - guard.y * cellSize;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < spy.radius + guard.radius) {
if (shieldActive) {
guards.splice(i, 1); // Щит уничтожает охранника
continue; // Пропускаем нанесение урона
} else {
playerScore -= 100;
document.getElementById("score").innerHTML = playerScore;
playerLives--;
document.getElementById("lives").innerHTML = generateHearts(playerLives, 5);
guards.splice(i, 1); // Удаляем охранника из массива
continue; // Переходим к следующему охраннику
}
}
}
if (playerLives <= 0) {
gameOver = true;
drawGameOver()
return;
}
// Проверяем столкновение с щитом
if (shield && spy.x >= shield.x && spy.x <= shield.x + 1 && spy.y >= shield.y && spy.y <= shield.y + 1) {
console.log("Подобрали щит!");
shieldActive = true;
shieldTimer = shieldDuration;
shield = null; // Удаляем щит с карты
}
// Пройдите лабиринт, чтобы найти выход.
for (let y = 0; y < mazeHeight; y++) {
for (let x = 0; x < mazeWidth; x++) {
if (maze[y][x] === 2) {
//Check if the spy is on the exit
if (spy.x === x && spy.y === y) {
showLevelCompleteScreen();
return; //Exit the function after showing level complete screen
}
}
}
}
if (heart && spy.x >= heart.x && spy.x <= heart.x + 1 && spy.y >= heart.y && spy.y <= heart.y + 1) {
playerLives++;
document.getElementById("lives").innerHTML = generateHearts(playerLives, 5);
heart = null;
}
// Проверяем столкновение с кусочками бумаги
for (let i = 0; i < paperPieces.length; i++) {
const piece = paperPieces[i];
if (spy.x >= piece.x && spy.x <= piece.x + 1 && spy.y >= piece.y && spy.y <= piece.y + 1) {
paperPieces.splice(i, 1); // Удаляем собранный кусочек
collectedPieces++; // Увеличиваем количество собранных кусочков
document.getElementById("collectedPieces").innerText = collectedPieces; // Обновляем значение элемента
break;
}
}
}
function showLevelCompleteScreen() {
if (collectedPieces === 5) {
gamePaused = true;
levelCompleteScreen.style.display = 'flex';
}
}
function levelComplete() {
playerScore += 500;
document.getElementById("score").innerText = playerScore;
currentLevel++;
if (currentLevel > maxLevels) {
gameWon = true;
} else {
playerLives = 5;
document.getElementById("lives").innerHTML = generateHearts(playerLives, 5);
document.getElementById("level").innerText = currentLevel;
initLevel();
gamePaused = false
}
}
function initLevel() {
maze = generateMaze();
placeGuards();
spy = { x: 1, y: 1, radius: cellSize / 2 - 2, color: 'blue' };
lastTime = performance.now();
for (let i = 0; i < guards.length; i++) {
guards[i].isChasing = false
}
if (currentLevel >= 2 && Math.random() < heartSpawnChance) {
generateHeart();
}
// Генерируем щит
if (currentLevel >= 1 && Math.random() < shieldSpawnChance) {
generateShield();
}
generatePaperPieces(); // Генерируем кусочки бумаги
collectedPieces = 0; // Сбрасываем количество собранных кусочков
generateExit();
}
function init() {
currentLevel = 1;
playerLives = 5;
playerScore = 500;
document.getElementById("lives").innerHTML = generateHearts(playerLives, 5);
document.getElementById("level").innerText = currentLevel;
document.getElementById("score").innerText = playerScore;
initLevel();
gameWon = false;
gameOver = false;
}
let lastTime = 0;
function gameLoop(currentTime) {
requestAnimationFrame(gameLoop);
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
if (gamePaused) {
return;
}
// Обновление таймера щита
if (shieldActive) {
shieldTimer -= deltaTime / 1000; // Преобразуем миллисекунды в секунды
if (shieldTimer <= 0) {
shieldActive = false;
shieldTimer = 0;
console.log("Действие щита закончилось!");
}
}
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
drawMaze();
drawSpy();
drawGuards();
moveGuards(deltaTime);
checkCollisions();
if (gameOver) {
gameOver = true;
gamePaused = true;
}
if (gameWon) {
drawWin();
return;
}
if (!gamePaused) {
elapsedTime = (Date.now() - startTime) / 1000;
document.getElementById("time").innerText = formatTime(elapsedTime);
}
}
function generateExit() {
let exitX, exitY;
let startX = 1;
let startY = 1;
let targetX, targetY;
do {
targetX = Math.floor(Math.random() * (mazeWidth - 2)) + 1;
targetY = Math.floor(Math.random() * (mazeHeight - 2)) + 1;
} while (maze[targetY]?.[targetX] === 1 || (targetX === startX && targetY === startY));
const path = aStar(startX, startY, targetX, targetY, maze);
if (path.length > 0) {
exitX = path[path.length - 1].x;
exitY = path[path.length - 1].y;
maze[exitY][exitX] = 2; // 2 означает выход
} else {
// Если путь не найден, устанавливаем выход в соседней клетке от старта
if (maze[1][2] === 0) {
exitX = 2;
exitY = 1;
} else {
exitX = 1;
exitY = 2;
}
maze[exitY][exitX] = 2;
}
}
function generatePaperPieces() {
paperPieces = [];
for (let i = 0; i < 5; i++) {
let x, y;
do {
x = Math.floor(Math.random() * mazeWidth);
y = Math.floor(Math.random() * mazeHeight);
} while (maze[y][x] !== 0 || (x === 1 && y === 1) || (x == mazeWidth - 2 && y == mazeHeight - 2) || isNearExistingPiece(x, y)); // Убеждаемся, что кусочек не появляется на стене, на старте, на финише или рядом с другим кусочком
paperPieces.push({ x: x, y: y });
}
}
function isNearExistingPiece(x, y) {
for (let i = 0; i < paperPieces.length; i++) {
const piece = paperPieces[i];
const dx = x - piece.x;
const dy = y - piece.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 2) { // Проверяем расстояние между кусочками
return true;
}
}
return false;
}
// === 9. Запуск игры ===
const hud = document.getElementById("hud")
const controls = document.getElementById("controls")
canvas.style.display = 'none'
hud.style.display = 'none'
controls.style.display = 'none'
document.getElementById("startGameButton").addEventListener("click", () => {
init()
gameLoop()
startScreen.style.display = "none"
controls.style.display = "block"
hud.style.display = "grid"
canvas.style.display = "block"
})
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment