Загрузка данных


<!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 {
            background: #e8e8e8;
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: 'Courier New', Courier, monospace;
            padding: 20px;
        }

        .game-container {
            background: #fdfdfd;
            border-radius: 4px;
            box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06);
            padding: 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
            position: relative;
        }

        canvas {
            display: block;
            background: #ffffff;
            border: 1px solid #ccc;
            border-radius: 2px;
            cursor: default;
            box-shadow: inset 0 0 20px rgba(0,0,0,0.02);
        }

        .hint {
            margin-top: 14px;
            font-size: 15px;
            color: #555;
            letter-spacing: 0.5px;
            text-align: center;
            user-select: none;
        }

        .hint kbd {
            background: #f0f0f0;
            border: 1px solid #bbb;
            border-radius: 4px;
            padding: 2px 8px;
            font-family: inherit;
            font-size: 13px;
            box-shadow: 0 1px 2px rgba(0,0,0,0.1);
        }

        /* Плашка "Задача" */
        .task-panel {
            position: absolute;
            top: 35px;
            left: 35px;
            background: #f8f8f8;
            border: 1px solid #ccc;
            border-radius: 4px;
            padding: 12px 18px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.08);
            user-select: none;
            pointer-events: none;
            z-index: 5;
        }

        .task-panel .task-title {
            font-weight: bold;
            font-size: 16px;
            color: #333;
            margin-bottom: 6px;
            letter-spacing: 1px;
        }

        .task-panel .task-text {
            font-size: 14px;
            color: #555;
            font-style: italic;
        }
    </style>
</head>
<body>
    <div class="game-container">
        <canvas id="gameCanvas" width="900" height="500"></canvas>
        <div class="task-panel">
            <div class="task-title">Задача</div>
            <div class="task-text">Изучить комнаты</div>
        </div>
        <div class="hint">
            <kbd>←</kbd>/<kbd>A</kbd> — влево &nbsp;|&nbsp; <kbd>→</kbd>/<kbd>D</kbd> — вправо &nbsp;|&nbsp; <kbd>E</kbd> — взаимодействие
        </div>
    </div>

    <script>
        (function() {
            const canvas = document.getElementById('gameCanvas');
            const ctx = canvas.getContext('2d');

            // Параметры мира
            const GROUND_Y = 390;
            const PLAYER_SCREEN_X = 450;
            const PLAYER_HEIGHT = 180;
            const HEAD_RADIUS = 22;
            const BODY_LENGTH = 80;
            const LEG_LENGTH = 50;
            const MOVE_SPEED = 4.5;

            // Состояние
            let worldOffsetX = 0;
            let isMovingLeft = false;
            let isMovingRight = false;
            let walkCycle = 0;
            let lastTimestamp = 0;
            let currentRoom = 'main';          // 'main' или 'room1'
            let isNearDoor = false;            // Игрок рядом с дверью

            // Граница слева
            const LEFT_BOUNDARY_DISTANCE = MOVE_SPEED * 60 * 10;
            const LEFT_BOUNDARY_WORLD_X = -LEFT_BOUNDARY_DISTANCE;
            const TEXT_WORLD_X = LEFT_BOUNDARY_WORLD_X + 150;
            const TEXT_WORLD_Y = GROUND_Y - 80;

            // Дверь справа
            const DOOR_WORLD_X = 1000;
            const DOOR_WIDTH = 70;
            const DOOR_HEIGHT = 160;
            const DOOR_TOP_Y = GROUND_Y - DOOR_HEIGHT;

            // Генерация стабильных штрихов для надписи "Прими смерть"
            const deathStrokes = [];
            const deathDots = [];
            (function generateDeathArt() {
                for (let i = 0; i < 8; i++) {
                    deathStrokes.push({
                        startX: -180 + Math.random() * 360,
                        startY: -40 + Math.random() * 80,
                        cp1x: 20 + Math.random() * 60,
                        cp1y: -10 + Math.random() * 30,
                        cp2x: 40 + Math.random() * 80,
                        cp2y: 5 + Math.random() * 20
                    });
                }
                for (let i = 0; i < 12; i++) {
                    deathDots.push({
                        x: -200 + Math.random() * 400,
                        y: -45 + Math.random() * 90,
                        r: Math.random() * 4 + 1
                    });
                }
            })();

            // Обработчики клавиш
            window.addEventListener('keydown', (e) => {
                const key = e.key.toLowerCase();

                if (currentRoom === 'main') {
                    if (key === 'arrowleft' || key === 'a') {
                        isMovingLeft = true;
                        e.preventDefault();
                    } else if (key === 'arrowright' || key === 'd') {
                        isMovingRight = true;
                        e.preventDefault();
                    } else if (key === 'e' && isNearDoor) {
                        enterRoom();
                    }
                } else if (currentRoom === 'room1') {
                    if (key === 'e') {
                        exitRoom();
                    }
                }

                // Блокируем прокрутку стрелками
                if (['arrowleft', 'arrowright', 'a', 'd'].includes(key)) {
                    e.preventDefault();
                }
            });

            window.addEventListener('keyup', (e) => {
                const key = e.key.toLowerCase();
                if (key === 'arrowleft' || key === 'a') {
                    isMovingLeft = false;
                    e.preventDefault();
                } else if (key === 'arrowright' || key === 'd') {
                    isMovingRight = false;
                    e.preventDefault();
                }
            });

            // Вход в комнату
            function enterRoom() {
                currentRoom = 'room1';
                isMovingLeft = false;
                isMovingRight = false;
            }

            // Выход из комнаты
            function exitRoom() {
                currentRoom = 'main';
                isMovingLeft = false;
                isMovingRight = false;
            }

            // Конвертация мировых координат в экранные
            function worldToScreen(worldX) {
                return worldX + worldOffsetX + PLAYER_SCREEN_X;
            }

            // Проверка близости к двери
            function checkDoorProximity() {
                if (currentRoom !== 'main') {
                    isNearDoor = false;
                    return;
                }
                const playerWorldX = -worldOffsetX;
                const distanceToDoor = Math.abs(playerWorldX - DOOR_WORLD_X);
                isNearDoor = distanceToDoor < 50;
            }

            // Рисование надписи "Прими смерть" углём (стабильные штрихи)
            function drawDeathMessage() {
                if (currentRoom !== 'main') return;
                const screenX = worldToScreen(TEXT_WORLD_X);
                const screenY = TEXT_WORLD_Y;

                if (screenX < -300 || screenX > canvas.width + 300) return;

                ctx.save();
                ctx.font = 'bold 48px "Courier New", Courier, monospace';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';

                // Тень
                ctx.fillStyle = 'rgba(0, 0, 0, 0.08)';
                ctx.fillText('Прими смерть', screenX + 4, screenY + 6);
                // Основной текст
                ctx.fillStyle = '#1a1a1a';
                ctx.fillText('Прими смерть', screenX, screenY);

                // Штрихи углём (стабильные)
                ctx.strokeStyle = 'rgba(0, 0, 0, 0.3)';
                ctx.lineWidth = 1.5;
                for (const stroke of deathStrokes) {
                    ctx.beginPath();
                    ctx.moveTo(screenX + stroke.startX, screenY + stroke.startY);
                    ctx.quadraticCurveTo(
                        screenX + stroke.startX + stroke.cp1x,
                        screenY + stroke.startY + stroke.cp1y,
                        screenX + stroke.startX + stroke.cp2x,
                        screenY + stroke.startY + stroke.cp2y
                    );
                    ctx.stroke();
                }

                // Точки-помарки (стабильные)
                ctx.fillStyle = 'rgba(0, 0, 0, 0.15)';
                for (const dot of deathDots) {
                    ctx.beginPath();
                    ctx.arc(screenX + dot.x, screenY + dot.y, dot.r, 0, Math.PI * 2);
                    ctx.fill();
                }

                ctx.restore();
            }

            // Рисование двери
            function drawDoor() {
                if (currentRoom !== 'main') return;
                const screenX = worldToScreen(DOOR_WORLD_X);

                if (screenX < -100 || screenX > canvas.width + 100) return;

                ctx.save();

                // Дверная коробка
                ctx.fillStyle = '#8b7355';
                ctx.fillRect(screenX - DOOR_WIDTH/2, DOOR_TOP_Y, DOOR_WIDTH, DOOR_HEIGHT);

                // Дверное полотно
                ctx.fillStyle = '#a0845c';
                ctx.fillRect(screenX - DOOR_WIDTH/2 + 5, DOOR_TOP_Y + 5, DOOR_WIDTH - 10, DOOR_HEIGHT - 5);

                // Ручка
                ctx.fillStyle = '#c0a060';
                ctx.beginPath();
                ctx.arc(screenX + DOOR_WIDTH/2 - 15, GROUND_Y - DOOR_HEIGHT/2, 5, 0, Math.PI * 2);
                ctx.fill();

                // Подсветка при приближении
                if (isNearDoor) {
                    ctx.fillStyle = 'rgba(255, 255, 200, 0.25)';
                    ctx.fillRect(screenX - DOOR_WIDTH/2 - 5, DOOR_TOP_Y - 5, DOOR_WIDTH + 10, DOOR_HEIGHT + 5);
                    ctx.font = 'bold 14px "Courier New", Courier, monospace';
                    ctx.fillStyle = '#333';
                    ctx.textAlign = 'center';
                    ctx.fillText('[E] Войти', screenX, DOOR_TOP_Y - 15);
                }

                ctx.restore();
            }

            // Рисование линии пола и границы
            function drawInfiniteGround() {
                if (currentRoom !== 'main') return;
                ctx.save();

                // Линия пола
                ctx.beginPath();
                ctx.moveTo(-2000, GROUND_Y);
                ctx.lineTo(2000, GROUND_Y);
                ctx.strokeStyle = '#2c2c2c';
                ctx.lineWidth = 2.5;
                ctx.stroke();

                // Граница слева
                const boundaryScreenX = worldToScreen(LEFT_BOUNDARY_WORLD_X);
                if (boundaryScreenX > -10 && boundaryScreenX < canvas.width + 10) {
                    ctx.beginPath();
                    ctx.moveTo(boundaryScreenX, GROUND_Y - 200);
                    ctx.lineTo(boundaryScreenX, GROUND_Y + 50);
                    ctx.strokeStyle = '#2c2c2c';
                    ctx.lineWidth = 2;
                    ctx.setLineDash([8, 6]);
                    ctx.stroke();
                    ctx.setLineDash([]);
                    ctx.fillStyle = 'rgba(0, 0, 0, 0.03)';
                    ctx.fillRect(boundaryScreenX, 0, canvas.width - boundaryScreenX, GROUND_Y);
                }

                // Деления
                const spacing = 80;
                const startMark = Math.floor(-worldOffsetX / spacing) * spacing;
                for (let x = startMark - spacing; x < canvas.width + spacing; x += spacing) {
                    const screenX = x + worldOffsetX + PLAYER_SCREEN_X;
                    if (screenX < -20 || screenX > canvas.width + 20) continue;
                    ctx.beginPath();
                    ctx.moveTo(screenX, GROUND_Y - 10);
                    ctx.lineTo(screenX, GROUND_Y + 10);
                    ctx.strokeStyle = '#aaa';
                    ctx.lineWidth = 1.2;
                    ctx.stroke();
                }

                ctx.restore();
            }

            // Рисование пустой комнаты
            function drawRoom() {
                ctx.save();

                // Белый фон
                ctx.fillStyle = '#ffffff';
                ctx.fillRect(0, 0, canvas.width, canvas.height);

                // Пол
                ctx.beginPath();
                ctx.moveTo(0, GROUND_Y);
                ctx.lineTo(canvas.width, GROUND_Y);
                ctx.strokeStyle = '#2c2c2c';
                ctx.lineWidth = 2.5;
                ctx.stroke();

                // Стены
                ctx.strokeStyle = '#ccc';
                ctx.lineWidth = 1.5;
                ctx.beginPath();
                ctx.moveTo(50, 50);
                ctx.lineTo(50, GROUND_Y);
                ctx.stroke();
                ctx.beginPath();
                ctx.moveTo(canvas.width - 50, 50);
                ctx.lineTo(canvas.width - 50, GROUND_Y);
                ctx.stroke();
                ctx.beginPath();
                ctx.moveTo(50, 50);
                ctx.lineTo(canvas.width - 50, 50);
                ctx.stroke();

                // Надпись
                ctx.font = 'italic 20px "Courier New", Courier, monospace';
                ctx.fillStyle = '#aaa';
                ctx.textAlign = 'center';
                ctx.fillText('Пустая комната', canvas.width / 2, GROUND_Y - 150);

                // Подсказка выхода
                ctx.fillStyle = '#f0f0f0';
                ctx.fillRect(canvas.width / 2 - 60, GROUND_Y + 50, 120, 40);
                ctx.strokeStyle = '#ccc';
                ctx.lineWidth = 1;
                ctx.strokeRect(canvas.width / 2 - 60, GROUND_Y + 50, 120, 40);
                ctx.fillStyle = '#555';
                ctx.font = '14px "Courier New", Courier, monospace';
                ctx.fillText('Выйти [E]', canvas.width / 2, GROUND_Y + 75);

                ctx.restore();
            }

            // Рисование человечка (главный мир)
            function drawPlayer() {
                if (currentRoom !== 'main') return;
                ctx.save();

                const playerX = PLAYER_SCREEN_X;
                let bounce = 0;
                if (isMovingLeft || isMovingRight) {
                    bounce = Math.sin(walkCycle * 2) * 1.5;
                }

                const headY = GROUND_Y - PLAYER_HEIGHT + HEAD_RADIUS + bounce;
                const neckY = headY + HEAD_RADIUS + 2;
                const hipY = neckY + BODY_LENGTH;
                const footY = GROUND_Y;

                const legSwing = (isMovingLeft || isMovingRight) ? Math.sin(walkCycle) * 12 : 0;

                // Левая нога
                const leftFootX = playerX - 8 + legSwing * 0.8;
                ctx.beginPath();
                ctx.moveTo(playerX - 4, hipY);
                ctx.lineTo(playerX - 5, hipY + LEG_LENGTH * 0.55);
                ctx.lineTo(leftFootX, footY);
                ctx.strokeStyle = '#1a1a1a';
                ctx.lineWidth = 2.8;
                ctx.lineCap = 'round';
                ctx.lineJoin = 'round';
                ctx.stroke();

                // Правая нога
                const rightFootX = playerX + 8 - legSwing * 0.8;
                ctx.beginPath();
                ctx.moveTo(playerX + 4, hipY);
                ctx.lineTo(playerX + 5, hipY + LEG_LENGTH * 0.55);
                ctx.lineTo(rightFootX, footY);
                ctx.stroke();

                // Туловище
                ctx.beginPath();
                ctx.moveTo(playerX, neckY);
                ctx.lineTo(playerX, hipY);
                ctx.strokeStyle = '#1a1a1a';
                ctx.lineWidth = 3.5;
                ctx.stroke();

                // Руки
                const armSwing = (isMovingLeft || isMovingRight) ? Math.sin(walkCycle + Math.PI) * 8 : 0;
                ctx.beginPath();
                ctx.moveTo(playerX - 1, neckY + 12);
                ctx.lineTo(playerX - 14 + armSwing * 0.6, neckY + 38);
                ctx.lineTo(playerX - 18 + armSwing * 0.9, neckY + 56);
                ctx.lineWidth = 2.2;
                ctx.stroke();
                ctx.beginPath();
                ctx.moveTo(playerX + 1, neckY + 12);
                ctx.lineTo(playerX + 14 - armSwing * 0.6, neckY + 38);
                ctx.lineTo(playerX + 18 - armSwing * 0.9, neckY + 56);
                ctx.stroke();

                // Голова
                ctx.beginPath();
                ctx.arc(playerX, headY, HEAD_RADIUS, 0, Math.PI * 2);
                ctx.strokeStyle = '#1a1a1a';
                ctx.lineWidth = 2.5;
                ctx.stroke();

                // Глаза
                const lookDirection = (isMovingLeft && !isMovingRight) ? -1 : (isMovingRight && !isMovingLeft) ? 1 : 0;
                const eyeOffsetX = lookDirection * 3;
                ctx.fillStyle = '#1a1a1a';
                ctx.beginPath();
                ctx.arc(playerX - 6 + eyeOffsetX, headY - 2, 2.2, 0, Math.PI * 2);
                ctx.fill();
                ctx.beginPath();
                ctx.arc(playerX + 6 + eyeOffsetX, headY - 2, 2.2, 0, Math.PI * 2);
                ctx.fill();

                ctx.restore();
            }

            // Обновление состояния
            function update() {
                if (currentRoom === 'main') {
                    const movingLeft = isMovingLeft && !isMovingRight;
                    const movingRight = isMovingRight && !isMovingLeft;

                    if (movingLeft) {
                        const newOffset = worldOffsetX + MOVE_SPEED;
                        const newPlayerWorldX = -newOffset;
                        if (newPlayerWorldX > LEFT_BOUNDARY_WORLD_X + 10) {
                            worldOffsetX = newOffset;
                        } else {
                            worldOffsetX = -(LEFT_BOUNDARY_WORLD_X + 10);
                        }
                    }

                    if (movingRight) {
                        worldOffsetX -= MOVE_SPEED;
                    }

                    if (movingLeft || movingRight) {
                        walkCycle += 0.18;
                    } else {
                        walkCycle *= 0.85;
                    }

                    checkDoorProximity();
                }
            }

            // Отрисовка
            function draw() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                if (currentRoom === 'main') {
                    // Белый лист
                    ctx.fillStyle = '#ffffff';
                    ctx.fillRect(0, 0, canvas.width, canvas.height);

                    // Лёгкая текстура бумаги
                    ctx.fillStyle = 'rgba(0,0,0,0.008)';
                    for (let i = 0; i < 20; i++) {
                        ctx.beginPath();
                        ctx.arc(
                            100 + Math.random() * 700,
                            80 + Math.random() * 350,
                            Math.random() * 30 + 10,
                            0, Math.PI * 2
                        );
                        ctx.fill();
                    }

                    drawInfiniteGround();
                    drawDeathMessage();
                    drawDoor();
                    drawPlayer();
                } else if (currentRoom === 'room1') {
                    drawRoom();