HTML (r3) (복원)


비로그인 상태입니다. 편집 시 지금 접속한 IP가 기록됩니다.

[[분류:프로그래밍 언어]]

==개요==
HTML은 세계 최고의 프로그래밍 언어이다! 개발자 도구로 각종 웹사이트를 들여다보면 그 코드가 HTML로 되어 있는 것을 볼 수 있을 것이다. 프로그래밍을 배우고 싶은 사람은 한번 HTML을 배워보도록 하자.

==HTML 파일을 만드는 법==
Visual Studio Code 같은 전문 프로그래밍 도구도 필요 없을 정도로 쉽다. 'gpt야 만들어줘'를 통해 3분이면 HTML 코드를 얻을 수 있다!

==이것을 이용한 작품==
===대항해 시대([[손현준(교사)|손현준]])===
[[손현준(교사)]]이 자신의 블로그 '번데기의 최후'에 올려 놓은 항해 게임이다. 코드는 아래와 같다.

{{{#!folding 전체 코드
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>🌋 화산 피하기 게임 🚗</title>
    <style>
        /* 기본 스타일 및 레이아웃 */
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            flex-direction: column;
        }

        #game-container {
            position: relative;
            width: 300px;
            height: 500px;
            background-color: #556b2f;
            border: 5px solid #000;
            overflow: hidden;
            touch-action: none;
        }

        /* 플레이어 스타일 */
        #player {
            position: absolute;
            width: 40px; 
            height: 40px;
            font-size: 35px;
            text-align: center;
            line-height: 40px;
            bottom: 10px;
            transform: rotate(90deg); 
            transition: transform 0.1s linear; 
            z-index: 10;
        }

        /* 장애물 공통 스타일 */
        .obstacle {
            position: absolute;
            box-sizing: border-box; 
        }

        /* 화산 스타일 */
        .volcano-spot {
            border-radius: 50%;
            opacity: 0;
            transition: opacity 0.2s linear;
        }
        .warning {
            background-color: sienna;
            opacity: 0.6;
            box-shadow: 0 0 8px rgba(160, 82, 45, 0.7);
        }
        .erupted {
            background-color: red;
            box-shadow: 0 0 15px 8px #ff4500;
            transform: scale(1.1);
            opacity: 1;
        }

        /* 검은 벽 장애물 스타일 */
        .black-wall {
            background-color: #2c2c2c;
            box-shadow: 0 0 5px rgba(0, 0, 0, 0.7);
            z-index: 5;
        }

        /* UI 요소 스타일 */
        .ui-overlay {
            position: absolute;
            top: 0; left: 0;
            width: 100%; height: 100%;
            background-color: rgba(0, 0, 0, 0.8);
            color: white;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            text-align: center;
            z-index: 20;
        }

        #game-title {
            position: absolute;
            top: 10px;
            width: 100%;
            text-align: center;
            color: white;
            font-size: 24px;
            font-weight: bold;
            text-shadow: 2px 2px 4px #000;
            z-index: 15;
        }

        #info-board {
            position: absolute;
            top: 5px; right: 5px;
            color: white; font-size: 14px;
            font-weight: bold;
            z-index: 15;
        }
        
        #game-over { display: none; }
        #start-screen { display: flex; }

        .game-button {
            padding: 10px 20px;
            font-size: 20px;
            cursor: pointer;
            margin-top: 30px;
            border: none;
            border-radius: 5px;
            background-color: #4CAF50;
            color: white;
        }
    </style>
</head>
<body>
    
    <div id="game-container">
        <div id="game-title">🌋 화산 피하기 🚗</div>
        <div id="info-board">
            거리: <span id="distance-display">0</span>m
        </div>
        
        <div id="player">🚗</div>

        <div id="start-screen" class="ui-overlay">
            <h1>화산 피하기</h1>
            <p>화산 폭발과 벽을 피해<br>최대한 멀리 도망가세요!</p>
            <button id="start-button" class="game-button">게임 시작</button>
        </div>
        <div id="game-over" class="ui-overlay">
            <h1>💥 게임 오버! 💥</h1>
            최종 점수: <span id="final-score">0</span><br>
            이동 거리: <span id="final-distance">0</span>m<br>
            <button id="restart-button" class="game-button">다시 시작</button>
        </div>
    </div>

    <script>
        // DOM 요소
        const gameContainer = document.getElementById('game-container');
        const player = document.getElementById('player');
        const distanceDisplay = document.getElementById('distance-display');
        const gameOverScreen = document.getElementById('game-over');
        const finalScoreSpan = document.getElementById('final-score');
        const finalDistanceSpan = document.getElementById('final-distance');
        const restartButton = document.getElementById('restart-button');
        const startScreen = document.getElementById('start-screen');
        const startButton = document.getElementById('start-button');

        // 게임 설정
        const CONTAINER_WIDTH = gameContainer.clientWidth;
        const CONTAINER_HEIGHT = gameContainer.clientHeight;
        const PLAYER_SIZE = 40;
        const PLAYER_SPEED = 4;
        const BASE_SCROLL_SPEED = 3.0;

        // 게임 상태 변수
        let isGameRunning = false;
        let score = 0, distance = 0;
        let playerX, playerY, playerAngle, playerScaleX;
        let keysPressed = {};
        let lastVolcanoTime = 0, lastWallTime = 0;
        let animationFrameId;

        function initGame() {
            isGameRunning = true;
            score = 0; distance = 0;
            playerX = CONTAINER_WIDTH / 2 - PLAYER_SIZE / 2;
            playerY = CONTAINER_HEIGHT - PLAYER_SIZE - 10;
            playerAngle = 90; playerScaleX = 1;
            distanceDisplay.textContent = '0';
            startScreen.style.display = 'none';
            gameOverScreen.style.display = 'none';

            document.querySelectorAll('.obstacle').forEach(el => el.remove());

            player.style.left = `${playerX}px`;
            player.style.top = `${playerY}px`;
            player.style.transform = `rotate(90deg) scaleX(1)`;
            player.style.visibility = 'visible';

            lastVolcanoTime = performance.now();
            lastWallTime = performance.now();
            
            if(animationFrameId) cancelAnimationFrame(animationFrameId);
            gameLoop();
        }

        function updatePlayer() {
            let dx = 0, dy = 0;
            if (keysPressed['ArrowLeft'] || keysPressed['a']) dx -= PLAYER_SPEED;
            if (keysPressed['ArrowRight'] || keysPressed['d']) dx += PLAYER_SPEED;
            if (keysPressed['ArrowUp'] || keysPressed['w']) dy -= PLAYER_SPEED;
            if (keysPressed['ArrowDown'] || keysPressed['s']) dy += PLAYER_SPEED;

            let nextX = playerX + dx;
            let nextY = playerY + dy;

            document.querySelectorAll('.black-wall').forEach(wall => {
                const wallRect = wall.getBoundingClientRect();
                const containerRect = gameContainer.getBoundingClientRect();
                const wallGameRect = {
                    left: wallRect.left - containerRect.left, top: wallRect.top - containerRect.top,
                    right: wallRect.right - containerRect.left, bottom: wallRect.bottom - containerRect.top
                };

                const hPlayerRect = { left: nextX, top: playerY, right: nextX + PLAYER_SIZE, bottom: playerY + PLAYER_SIZE };
                if (hPlayerRect.right > wallGameRect.left && hPlayerRect.left < wallGameRect.right &&
                    hPlayerRect.bottom > wallGameRect.top && hPlayerRect.top < wallGameRect.bottom) {
                    nextX = playerX;
                }

                const vPlayerRect = { left: playerX, top: nextY, right: playerX + PLAYER_SIZE, bottom: nextY + PLAYER_SIZE };
                if (vPlayerRect.right > wallGameRect.left && vPlayerRect.left < wallGameRect.right &&
                    vPlayerRect.bottom > wallGameRect.top && vPlayerRect.top < wallGameRect.bottom) {
                    nextY = playerY;
                }
            });

            playerX = Math.max(0, Math.min(CONTAINER_WIDTH - PLAYER_SIZE, nextX));
            playerY = Math.max(0, Math.min(CONTAINER_HEIGHT - PLAYER_SIZE, nextY));

            if (dx !== 0 || dy !== 0) {
                 if (dy < 0) playerAngle = dx === 0 ? 90 : (dx < 0 ? 45 : 135);
                 else if (dy > 0) playerAngle = dx === 0 ? -90 : (dx < 0 ? -45 : -135);
                 else playerAngle = 0;
                 if (dx < 0) playerScaleX = 1; else if (dx > 0) playerScaleX = -1;
            }
            
            player.style.left = `${playerX}px`;
            player.style.top = `${playerY}px`;
            player.style.transform = `rotate(${playerAngle}deg) scaleX(${playerScaleX})`;
        }

        function isOverlapping(newRect) {
            let overlapping = false;
            document.querySelectorAll('.obstacle').forEach(obstacle => {
                const existingRect = obstacle.getBoundingClientRect();
                const containerRect = gameContainer.getBoundingClientRect();
                 const existingGameRect = {
                    left: existingRect.left - containerRect.left, top: existingRect.top - containerRect.top,
                    right: existingRect.right - containerRect.left, bottom: existingRect.bottom - containerRect.top
                };
                const padding = 10; 
                if (newRect.right + padding > existingGameRect.left && newRect.left - padding < existingGameRect.right &&
                    newRect.bottom + padding > existingGameRect.top && newRect.top - padding < existingGameRect.bottom) {
                    overlapping = true;
                }
            });
            return overlapping;
        }

        function createVolcano(timestamp, difficultyLevel) {
            const interval = Math.max(500, 1600 - difficultyLevel * 150);
            if (timestamp - lastVolcanoTime < interval) return;
            lastVolcanoTime = timestamp;
            
            let newRect, w, h, x, y;
            for (let i = 0; i < 10; i++) {
                const minSize = Math.min(50, 30 + difficultyLevel * 4);
                const maxSize = Math.min(90, 60 + difficultyLevel * 6);
                const size = Math.random() * (maxSize - minSize) + minSize;

                w = size; h = size;
                if (Math.random() < 0.5) h = Math.random() * (maxSize - minSize) + minSize;
                x = Math.random() * (CONTAINER_WIDTH - w);
                y = -h;
                newRect = { left: x, top: y, right: x + w, bottom: y + h };
                if (!isOverlapping(newRect)) break;
                if (i === 9) return;
            }

            const volcano = document.createElement('div');
            volcano.className = 'obstacle volcano-spot';
            volcano.style.width = `${w}px`; volcano.style.height = `${h}px`;
            volcano.style.left = `${x}px`; volcano.style.top = `${y}px`;
            gameContainer.appendChild(volcano);

            setTimeout(() => volcano.classList.add('warning'), 600);
            setTimeout(() => {
                volcano.classList.remove('warning');
                volcano.classList.add('erupted');
            }, 800);
            setTimeout(() => volcano.remove(), 3000);
        }

        function createBlackWall(timestamp, difficultyLevel) {
            const interval = Math.max(1200, 2800 - difficultyLevel * 200);
            if (timestamp - lastWallTime < interval) return;
            lastWallTime = timestamp;

            let newRect, width, angle, x, y;
            for (let i = 0; i < 10; i++) {
                const minWidth = Math.min(PLAYER_SIZE * 1.5, PLAYER_SIZE + difficultyLevel * 5);
                const maxWidth = Math.min(PLAYER_SIZE * 2.5, PLAYER_SIZE * 2 + difficultyLevel * 8);
                const WALL_HEIGHT = 20;
                width = Math.random() * (maxWidth - minWidth) + minWidth;
                
                angle = Math.random() * 180 - 90;
                x = Math.random() * (CONTAINER_WIDTH - width);
                y = -WALL_HEIGHT;
                newRect = { left: x, top: y, right: x + width, bottom: y + WALL_HEIGHT };
                if (!isOverlapping(newRect)) break;
                if (i === 9) return;
            }

            const wall = document.createElement('div');
            wall.className = 'obstacle black-wall';
            wall.style.width = `${width}px`; wall.style.height = `20px`;
            wall.style.left = `${x}px`; wall.style.top = `${y}px`;
            wall.style.transform = `rotate(${angle}deg)`;
            gameContainer.appendChild(wall);
            setTimeout(() => wall.remove(), 5000);
        }

        function updateObstacles(currentScrollSpeed) {
            document.querySelectorAll('.obstacle').forEach(obstacle => {
                let top = parseFloat(obstacle.style.top) || 0;
                obstacle.style.top = `${top + currentScrollSpeed}px`;
                
                if (obstacle.classList.contains('erupted')) {
                    checkVolcanoCollision(obstacle);
                }

                if (obstacle.classList.contains('black-wall')) {
                    const playerRect = { left: playerX, top: playerY, right: playerX + PLAYER_SIZE, bottom: playerY + PLAYER_SIZE };
                    const wallRect = obstacle.getBoundingClientRect();
                    const containerRect = gameContainer.getBoundingClientRect();
                    const wallGameRect = {
                        left: wallRect.left - containerRect.left, top: wallRect.top - containerRect.top,
                        right: wallRect.right - containerRect.left, bottom: wallRect.bottom - containerRect.top
                    };
                    
                    if (playerRect.right > wallGameRect.left && playerRect.left < wallGameRect.right &&
                        playerRect.bottom > wallGameRect.top && playerRect.top < wallGameRect.bottom) {
                        
                        // ⭐ [수정됨] 벽이 플레이어를 밀어낼 때, 즉시 화면에 반영
                        playerY = wallGameRect.bottom;
                        player.style.top = `${playerY}px`; 
                    }
                }
            });
        }
        
        function checkVolcanoCollision(volcano) {
            if (!isGameRunning) return;
            const playerRect = player.getBoundingClientRect();
            const volcanoRect = volcano.getBoundingClientRect();
            if (playerRect.right > volcanoRect.left && playerRect.left < volcanoRect.right &&
                playerRect.bottom > volcanoRect.top && playerRect.top < volcanoRect.bottom) {
                endGame();
            }
        }

        function endGame() {
            if (!isGameRunning) return;
            isGameRunning = false;
            cancelAnimationFrame(animationFrameId);
            player.style.visibility = 'hidden';
            finalScoreSpan.textContent = Math.floor(score);
            finalDistanceSpan.textContent = Math.floor(distance);
            gameOverScreen.style.display = 'flex';
        }
        
        function gameLoop(timestamp) {
            if (!isGameRunning) return;
            
            const difficultyLevel = Math.floor(distance / 200);
            const currentScrollSpeed = BASE_SCROLL_SPEED + difficultyLevel * 0.25;
            
            updatePlayer();
            createVolcano(timestamp, difficultyLevel);
            createBlackWall(timestamp, difficultyLevel);
            updateObstacles(currentScrollSpeed);
            
            score += currentScrollSpeed * 0.5;
            distance += currentScrollSpeed * 0.1;
            distanceDisplay.textContent = `${Math.floor(distance)}`;

            if (playerY >= CONTAINER_HEIGHT - PLAYER_SIZE) {
                endGame();
            }
            
            animationFrameId = requestAnimationFrame(gameLoop);
        }

        // 이벤트 리스너
        document.addEventListener('keydown', (e) => { keysPressed[e.key] = true; });
        document.addEventListener('keyup', (e) => { keysPressed[e.key] = false; });
        startButton.addEventListener('click', initGame);
        restartButton.addEventListener('click', initGame);
    </script>
</body>
</html>
}}}