// DOM 요소 가져오기 const canvas = document.getElementById('tetris-board'); const context = canvas.getContext('2d'); const scoreElement = document.getElementById('score'); const startButton = document.getElementById('start-button'); // 게임 보드 설정 const ROWS = 20; const COLS = 10; const BLOCK_SIZE = 30; context.canvas.width = COLS * BLOCK_SIZE; context.canvas.height = ROWS * BLOCK_SIZE; // 블록 색상 및 모양 정의 const COLORS = [null, '#FF0D72', '#0DC2FF', '#0DFF72', '#F538FF', '#FF8E0D', '#FFE138', '#3877FF']; const SHAPES = [ [], // 0번 인덱스는 비워둠 [[1, 1, 1, 1]], // I [[1, 1, 1], [0, 1, 0]], // T [[1, 1, 1], [1, 0, 0]], // L [[1, 1, 1], [0, 0, 1]], // J [[1, 1, 0], [0, 1, 1]], // S [[0, 1, 1], [1, 1, 0]], // Z [[1, 1], [1, 1]] // O ]; let board = Array.from({ length: ROWS }, () => Array(COLS).fill(0)); let score = 0; let gameOver = false; let currentPiece; let gameInterval; // 플레이어(현재 블록) 객체 class Piece { constructor(shape, color) { this.shape = shape; this.color = color; this.x = Math.floor(COLS / 2) - Math.floor(shape[0].length / 2); this.y = 0; } draw() { context.fillStyle = this.color; this.shape.forEach((row, y) => { row.forEach((value, x) => { if (value > 0) { context.fillRect((this.x + x) * BLOCK_SIZE, (this.y + y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE); } }); }); } move(dx, dy) { this.x += dx; this.y += dy; } } // 새로운 블록 생성 function generateNewPiece() { const rand = Math.floor(Math.random() * (SHAPES.length - 1)) + 1; const shape = SHAPES[rand]; const color = COLORS[rand]; return new Piece(shape, color); } // 게임 보드 그리기 function drawBoard() { board.forEach((row, y) => { row.forEach((value, x) => { if (value > 0) { context.fillStyle = COLORS[value]; context.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE); } }); }); } // 전체 화면 다시 그리기 function draw() { context.clearRect(0, 0, canvas.width, canvas.height); drawBoard(); if (currentPiece) { currentPiece.draw(); } } // 충돌 감지 function isValidMove(piece, dx = 0, dy = 0) { return piece.shape.every((row, y) => { return row.every((value, x) => { if (value === 0) { return true; } const newX = piece.x + x + dx; const newY = piece.y + y + dy; return newX >= 0 && newX < COLS && newY < ROWS && (board[newY] && board[newY][newX] === 0); }); }); } // 블록 회전 function rotatePiece() { const shape = currentPiece.shape; const newShape = shape[0].map((_, colIndex) => shape.map(row => row[colIndex]).reverse()); const originalX = currentPiece.x; let offsetX = 1; const tempPiece = { ...currentPiece, shape: newShape }; if (isValidMove(tempPiece)) { currentPiece.shape = newShape; return; } while (true) { if (isValidMove(tempPiece, offsetX)) { currentPiece.x += offsetX; currentPiece.shape = newShape; return; } if (offsetX > 0) { if (offsetX > tempPiece.shape[0].length) break; offsetX = -(offsetX + 1); } else { if (Math.abs(offsetX) > tempPiece.shape[0].length) break; offsetX = -(offsetX - 1); } } currentPiece.x = originalX; } // 블록을 보드에 고정 function lockPiece() { currentPiece.shape.forEach((row, y) => { row.forEach((value, x) => { if (value > 0) { const boardY = currentPiece.y + y; const boardX = currentPiece.x + x; if (boardY < ROWS) { const colorIndex = COLORS.indexOf(currentPiece.color); board[boardY][boardX] = colorIndex > 0 ? colorIndex : 1; } } }); }); } // 줄 제거 function clearLines() { let linesCleared = 0; for (let y = ROWS - 1; y >= 0; y--) { if (board[y].every(value => value > 0)) { linesCleared++; board.splice(y, 1); board.unshift(Array(COLS).fill(0)); y++; } } if (linesCleared > 0) { score += linesCleared * 100; scoreElement.textContent = score; } } // 게임 오버 확인 function checkGameOver() { if (!isValidMove(currentPiece)) { gameOver = true; clearInterval(gameInterval); alert(`Game Over! Your score: ${score}`); startButton.textContent = "Restart Game"; } } // --- 추가된 부분: 하드 드롭 함수 --- function hardDrop() { // 이동 가능한 동안 계속 아래로 이동시킴 while (isValidMove(currentPiece, 0, 1)) { currentPiece.move(0, 1); score += 2; // 하드 드롭 보너스 점수 } scoreElement.textContent = score; // 블록이 멈춘 후 바로 다음 게임 루프 실행 (고정, 줄 제거, 새 블록 생성) gameLoop(); } // 게임 루프 function gameLoop() { if (gameOver) return; if (isValidMove(currentPiece, 0, 1)) { currentPiece.move(0, 1); } else { lockPiece(); clearLines(); currentPiece = generateNewPiece(); checkGameOver(); } draw(); } // --- 수정된 부분: 키보드 이벤트 처리 --- document.addEventListener('keydown', (event) => { if (gameOver || !currentPiece) return; switch (event.key) { case 'ArrowLeft': if (isValidMove(currentPiece, -1, 0)) currentPiece.move(-1, 0); break; case 'ArrowRight': if (isValidMove(currentPiece, 1, 0)) currentPiece.move(1, 0); break; case 'ArrowDown': if (isValidMove(currentPiece, 0, 1)) { currentPiece.move(0, 1); score += 1; scoreElement.textContent = score; } else { gameLoop(); } break; case 'ArrowUp': rotatePiece(); break; case ' ': // 스페이스바 키 event.preventDefault(); // 스페이스바의 기본 동작(페이지 스크롤) 방지 hardDrop(); break; } draw(); }); // 게임 시작 function startGame() { board = Array.from({ length: ROWS }, () => Array(COLS).fill(0)); score = 0; scoreElement.textContent = score; gameOver = false; currentPiece = generateNewPiece(); if (gameInterval) clearInterval(gameInterval); gameInterval = setInterval(gameLoop, 800); draw(); startButton.textContent = "Start Game"; } startButton.addEventListener('click', startGame); // 초기 안내 context.fillStyle = 'white'; context.font = '20px Arial'; context.textAlign = 'center'; context.fillText('Click "Start Game" to play!', canvas.width / 2, canvas.height / 2);