Spaces:
Running
Running
| <html lang="ko"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>HTML 지렁이 게임</title> | |
| <style> | |
| /* 기본 스타일 */ | |
| body { | |
| background-color: #f0f0f0; | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| height: 100vh; | |
| margin: 0; | |
| } | |
| /* 게임 컨테이너 스타일 */ | |
| #game-container { | |
| background-color: #fff; | |
| border-radius: 10px; | |
| box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |
| padding: 20px; | |
| text-align: center; | |
| } | |
| h1 { | |
| color: #333; | |
| margin-bottom: 10px; | |
| } | |
| /* 게임 캔버스 스타일 */ | |
| #gameCanvas { | |
| background-color: #dcedc8; /* 연한 녹색 배경 */ | |
| border: 2px solid #333; | |
| } | |
| /* 점수판 스타일 */ | |
| #score-board { | |
| font-size: 24px; | |
| font-weight: bold; | |
| color: #555; | |
| margin-top: 10px; | |
| } | |
| /* 게임 오버 메시지 스타일 */ | |
| #game-over { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background-color: rgba(0, 0, 0, 0.7); | |
| color: white; | |
| padding: 20px 40px; | |
| border-radius: 10px; | |
| display: none; /* 기본적으로 숨김 */ | |
| text-align: center; | |
| } | |
| #game-over h2 { | |
| margin: 0 0 10px 0; | |
| } | |
| #restart-button { | |
| background-color: #4CAF50; | |
| color: white; | |
| border: none; | |
| padding: 10px 20px; | |
| font-size: 16px; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| transition: background-color 0.3s; | |
| } | |
| #restart-button:hover { | |
| background-color: #45a049; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="game-container"> | |
| <h1>지렁이 게임</h1> | |
| <canvas id="gameCanvas" width="400" height="400"></canvas> | |
| <div id="score-board">점수: <span id="score">0</span></div> | |
| </div> | |
| <div id="game-over"> | |
| <h2>게임 오버!</h2> | |
| <button id="restart-button">다시 시작</button> | |
| </div> | |
| <script> | |
| // --- 1. 초기 설정 --- | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const scoreElement = document.getElementById('score'); | |
| const gameOverElement = document.getElementById('game-over'); | |
| const restartButton = document.getElementById('restart-button'); | |
| const BOX_SIZE = 20; // 각 칸의 크기 | |
| const CANVAS_WIDTH = canvas.width; | |
| const CANVAS_HEIGHT = canvas.height; | |
| let snake; // 지렁이 배열 (x, y 좌표 객체) | |
| let food; // 먹이 객체 (x, y 좌표) | |
| let score; // 점수 | |
| let direction; // 현재 이동 방향 | |
| let changingDirection; // 방향 전환 중인지 확인 (연속적인 키 입력 방지) | |
| let gameLoop; // 게임 루프 setInterval ID | |
| // --- 2. 게임 시작 및 재시작 --- | |
| function startGame() { | |
| // 게임 상태 초기화 | |
| snake = [ | |
| { x: 10 * BOX_SIZE, y: 10 * BOX_SIZE }, | |
| { x: 9 * BOX_SIZE, y: 10 * BOX_SIZE }, | |
| { x: 8 * BOX_SIZE, y: 10 * BOX_SIZE } | |
| ]; | |
| score = 0; | |
| direction = 'RIGHT'; | |
| changingDirection = false; | |
| // UI 초기화 | |
| scoreElement.textContent = score; | |
| gameOverElement.style.display = 'none'; | |
| generateFood(); // 첫 먹이 생성 | |
| // 기존 게임 루프가 있다면 중지 | |
| if (gameLoop) { | |
| clearInterval(gameLoop); | |
| } | |
| // 새로운 게임 루프 시작 (100ms 마다 main 함수 실행) | |
| gameLoop = setInterval(main, 100); | |
| } | |
| // --- 3. 게임 메인 루프 --- | |
| function main() { | |
| if (checkGameOver()) { | |
| clearInterval(gameLoop); | |
| gameOverElement.style.display = 'block'; | |
| return; | |
| } | |
| changingDirection = false; | |
| clearCanvas(); | |
| drawFood(); | |
| moveSnake(); | |
| drawSnake(); | |
| } | |
| // --- 4. 그리기 함수들 --- | |
| // 캔버스 지우기 | |
| function clearCanvas() { | |
| ctx.fillStyle = '#dcedc8'; | |
| ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); | |
| } | |
| // 지렁이 그리기 | |
| function drawSnake() { | |
| snake.forEach((segment, index) => { | |
| // 머리 색상 다르게 하기 | |
| ctx.fillStyle = (index === 0) ? '#1b5e20' : '#4caf50'; | |
| ctx.strokeStyle = '#388e3c'; | |
| ctx.fillRect(segment.x, segment.y, BOX_SIZE, BOX_SIZE); | |
| ctx.strokeRect(segment.x, segment.y, BOX_SIZE, BOX_SIZE); | |
| }); | |
| } | |
| // 먹이 그리기 | |
| function drawFood() { | |
| ctx.fillStyle = '#d32f2f'; // 붉은색 먹이 | |
| ctx.strokeStyle = '#c62828'; | |
| ctx.fillRect(food.x, food.y, BOX_SIZE, BOX_SIZE); | |
| ctx.strokeRect(food.x, food.y, BOX_SIZE, BOX_SIZE); | |
| } | |
| // --- 5. 게임 로직 함수들 --- | |
| // 먹이 생성 (지렁이 몸 위가 아닌 곳에) | |
| function generateFood() { | |
| while (true) { | |
| const foodX = Math.floor(Math.random() * (CANVAS_WIDTH / BOX_SIZE)) * BOX_SIZE; | |
| const foodY = Math.floor(Math.random() * (CANVAS_HEIGHT / BOX_SIZE)) * BOX_SIZE; | |
| let isFoodOnSnake = snake.some(segment => segment.x === foodX && segment.y === foodY); | |
| if (!isFoodOnSnake) { | |
| food = { x: foodX, y: foodY }; | |
| return; | |
| } | |
| } | |
| } | |
| // 지렁이 이동 | |
| function moveSnake() { | |
| let dx = 0; | |
| let dy = 0; | |
| if (direction === 'RIGHT') dx = BOX_SIZE; | |
| if (direction === 'LEFT') dx = -BOX_SIZE; | |
| if (direction === 'UP') dy = -BOX_SIZE; | |
| if (direction === 'DOWN') dy = BOX_SIZE; | |
| const head = { x: snake[0].x + dx, y: snake[0].y + dy }; | |
| snake.unshift(head); // 새로운 머리를 배열 맨 앞에 추가 | |
| // 먹이를 먹었는지 확인 | |
| if (head.x === food.x && head.y === food.y) { | |
| score += 10; | |
| scoreElement.textContent = score; | |
| generateFood(); | |
| } else { | |
| snake.pop(); // 먹이를 먹지 않았다면 꼬리 제거 | |
| } | |
| } | |
| // 게임 오버 조건 확인 | |
| function checkGameOver() { | |
| // 1. 벽 충돌 확인 | |
| const head = snake[0]; | |
| if (head.x < 0 || head.x >= CANVAS_WIDTH || head.y < 0 || head.y >= CANVAS_HEIGHT) { | |
| return true; | |
| } | |
| // 2. 자기 몸 충돌 확인 | |
| for (let i = 1; i < snake.length; i++) { | |
| if (head.x === snake[i].x && head.y === snake[i].y) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| // --- 6. 이벤트 리스너 --- | |
| // 키보드 입력 처리 | |
| function changeDirection(event) { | |
| if (changingDirection) return; | |
| changingDirection = true; | |
| const keyPressed = event.key; | |
| const goingUp = direction === 'UP'; | |
| const goingDown = direction === 'DOWN'; | |
| const goingRight = direction === 'RIGHT'; | |
| const goingLeft = direction === 'LEFT'; | |
| if ((keyPressed === 'ArrowLeft' || keyPressed.toLowerCase() === 'a') && !goingRight) { | |
| direction = 'LEFT'; | |
| } | |
| if ((keyPressed === 'ArrowUp' || keyPressed.toLowerCase() === 'w') && !goingDown) { | |
| direction = 'UP'; | |
| } | |
| if ((keyPressed === 'ArrowRight' || keyPressed.toLowerCase() === 'd') && !goingLeft) { | |
| direction = 'RIGHT'; | |
| } | |
| if ((keyPressed === 'ArrowDown' || keyPressed.toLowerCase() === 's') && !goingUp) { | |
| direction = 'DOWN'; | |
| } | |
| } | |
| document.addEventListener('keydown', changeDirection); | |
| restartButton.addEventListener('click', startGame); | |
| // --- 7. 게임 시작 --- | |
| startGame(); | |
| </script> | |
| </body> | |
| </html> |