질문 :

 

자바스크립트로 테트리스 만들어줘 

 

실행  화면 :

답변 :

 

좋아요! 이번에는 JavaScript + HTML + Canvas 로 테트리스 만드는 버전을 보여드릴게요.
브라우저에서 바로 실행할 수 있도록 단일 HTML 파일로 구성했습니다.

완전한 단일 파일 테트리스 (JavaScript 버전)

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8" />
  <title>자바스크립트 테트리스</title>
  <style>
    body {
      margin: 0;
      height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
      background: #111;
      font-family: Arial, sans-serif;
      color: #eee;
    }
    #container {
      position: relative;
    }
    canvas {
      background: #000;
      border: 2px solid #444;
      image-rendering: pixelated;
    }
    #score {
      position: absolute;
      top: 20px;
      left: 340px;
      font-size: 24px;
    }
    #next {
      position: absolute;
      top: 80px;
      left: 340px;
    }
    #gameover {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      font-size: 60px;
      color: #f33;
      display: none;
      text-shadow: 0 0 10px #f00;
    }
  </style>
</head>
<body>

<div id="container">
  <canvas id="tetris" width="300" height="600"></canvas>
  <div id="score">점수: 0</div>
  <div id="next">NEXT</div>
  <div id="gameover">GAME OVER</div>
</div>

<script>
// ================================
// 기본 설정
// ================================
const canvas = document.getElementById('tetris');
const ctx = canvas.getContext('2d');
const COLS = 10;
const ROWS = 20;
const BLOCK = 30;

const colors = [
  null,
  '#00FFFF', // I
  '#FFFF00', // O
  '#800080', // T
  '#00FF00', // S
  '#FF0000', // Z
  '#0000FF', // J
  '#FFA500'  // L
];

const shapes = [
  [], // 0: 빈칸
  [[1,1,1,1]],           // I
  [[1,1],[1,1]],         // O
  [[0,1,0],[1,1,1]],     // T
  [[0,1,1],[1,1,0]],     // S
  [[1,1,0],[0,1,1]],     // Z
  [[1,0,0],[1,1,1]],     // J
  [[0,0,1],[1,1,1]]      // L
];

// ================================
// 게임 상태
// ================================
let board = Array(ROWS).fill().map(() => Array(COLS).fill(0));
let piece = null;
let nextPiece = null;
let score = 0;
let dropCounter = 0;
let dropInterval = 1000; // ms
let lastTime = 0;
let gameOver = false;

const scoreElement = document.getElementById('score');
const gameoverElement = document.getElementById('gameover');

// ================================
// 테트로미노 클래스 비슷한 객체
// ================================
function createPiece() {
  const type = Math.floor(Math.random() * (shapes.length - 1)) + 1;
  return {
    type: type,
    shape: shapes[type].map(row => [...row]),
    x: Math.floor(COLS / 2) - 1,
    y: 0
  };
}

function rotate(piece) {
  // 90도 시계 방향 회전
  const rotated = piece.shape[0].map((_, i) =>
    piece.shape.map(row => row[i]).reverse()
  );
  piece.shape = rotated;
}

// ================================
// 충돌 체크
// ================================
function valid(piece, dx = 0, dy = 0) {
  for (let y = 0; y < piece.shape.length; y++) {
    for (let x = 0; x < piece.shape[y].length; x++) {
      if (piece.shape[y][x]) {
        const newX = piece.x + x + dx;
        const newY = piece.y + y + dy;
        if (newX < 0 || newX >= COLS || newY >= ROWS) return false;
        if (newY >= 0 && board[newY][newX]) return false;
      }
    }
  }
  return true;
}

// ================================
// 블록 고정 & 라인 제거
// ================================
function merge() {
  for (let y = 0; y < piece.shape.length; y++) {
    for (let x = 0; x < piece.shape[y].length; x++) {
      if (piece.shape[y][x]) {
        board[piece.y + y][piece.x + x] = piece.type;
      }
    }
  }
}

function sweep() {
  let lines = 0;
  outer: for (let y = ROWS - 1; y >= 0; --y) {
    for (let x = 0; x < COLS; ++x) {
      if (board[y][x] === 0) {
        continue outer;
      }
    }
    // 라인 삭제
    board.splice(y, 1);
    board.unshift(Array(COLS).fill(0));
    lines++;
    y++; // 다시 같은 y 체크 (한 줄 위로 당겨짐)
  }
  if (lines > 0) {
    score += lines * 100;
    scoreElement.textContent = `점수: ${score}`;
    if (lines >= 4) console.log("TETRIS!");
  }
}

// ================================
// 그리기 함수
// ================================
function drawBlock(x, y, type) {
  ctx.fillStyle = colors[type];
  ctx.fillRect(x * BLOCK, y * BLOCK, BLOCK, BLOCK);
  ctx.strokeStyle = '#222';
  ctx.lineWidth = 2;
  ctx.strokeRect(x * BLOCK, y * BLOCK, BLOCK, BLOCK);
}

function drawBoard() {
  for (let y = 0; y < ROWS; y++) {
    for (let x = 0; x < COLS; x++) {
      if (board[y][x]) {
        drawBlock(x, y, board[y][x]);
      }
    }
  }
}

function drawPiece() {
  for (let y = 0; y < piece.shape.length; y++) {
    for (let x = 0; x < piece.shape[y].length; x++) {
      if (piece.shape[y][x]) {
        drawBlock(piece.x + x, piece.y + y, piece.type);
      }
    }
  }
}

function drawNext() {
  const nextCanvas = document.createElement('canvas');
  nextCanvas.width = 120;
  nextCanvas.height = 120;
  const nctx = nextCanvas.getContext('2d');
  nctx.fillStyle = '#000';
  nctx.fillRect(0,0,120,120);

  for (let y = 0; y < nextPiece.shape.length; y++) {
    for (let x = 0; x < nextPiece.shape[y].length; x++) {
      if (nextPiece.shape[y][x]) {
        nctx.fillStyle = colors[nextPiece.type];
        nctx.fillRect((x+1)*30, (y+1)*30, 28, 28);
        nctx.strokeStyle = '#222';
        nctx.strokeRect((x+1)*30, (y+1)*30, 28, 28);
      }
    }
  }
  document.getElementById('next').innerHTML = 'NEXT<br>';
  document.getElementById('next').appendChild(nextCanvas);
}

function draw() {
  ctx.fillStyle = '#000';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  drawBoard();
  drawPiece();
}

// ================================
// 게임 루프
// ================================
function update(time = 0) {
  if (gameOver) return;

  const deltaTime = time - lastTime;
  lastTime = time;

  dropCounter += deltaTime;

  if (dropCounter > dropInterval) {
    piece.y++;
    if (!valid(piece)) {
      piece.y--;
      merge();
      sweep();
      piece = nextPiece;
      nextPiece = createPiece();
      if (!valid(piece)) {
        gameOver = true;
        gameoverElement.style.display = 'block';
      }
    }
    dropCounter = 0;
  }

  draw();
  requestAnimationFrame(update);
}

// ================================
// 키보드 입력
// ================================
document.addEventListener('keydown', e => {
  if (gameOver) return;

  if (e.key === 'ArrowLeft' || e.key === 'a') {
    if (valid(piece, -1)) piece.x--;
  }
  else if (e.key === 'ArrowRight' || e.key === 'd') {
    if (valid(piece, 1)) piece.x++;
  }
  else if (e.key === 'ArrowDown' || e.key === 's') {
    if (valid(piece, 0, 1)) {
      piece.y++;
      dropCounter = 0;
    }
  }
  else if (e.key === 'ArrowUp' || e.key === 'w') {
    const oldShape = piece.shape.map(row => [...row]);
    rotate(piece);
    if (!valid(piece)) {
      piece.shape = oldShape;
    }
  }
  else if (e.key === ' ') { // 스페이스 : 하드 드롭
    while (valid(piece, 0, 1)) {
      piece.y++;
    }
    piece.y--;
  }
});

// ================================
// 게임 시작
// ================================
function start() {
  board = Array(ROWS).fill().map(() => Array(COLS).fill(0));
  piece = createPiece();
  nextPiece = createPiece();
  score = 0;
  scoreElement.textContent = '점수: 0';
  gameOver = false;
  gameoverElement.style.display = 'none';
  dropCounter = 0;
  dropInterval = 1000;
  update();
}

start();
</script>
</body>
</html>

사용법

  1. 위 코드를 tetris.html 로 저장
  2. 더블클릭해서 브라우저에서 열기
  3. 조작키
    ← → / A D : 좌우 이동
    ↓ / S : 소프트 드롭
    ↑ / W : 회전
    스페이스바 : 하드 드롭 (바닥까지 바로 떨어짐)

지금 단계에서 추가하면 좋은 기능들 (난이도 순)

  • 레벨 업 (점수 따라 dropInterval 줄이기)
  • 홀드 기능 (C 키)
  • 그림자(고스트) 블록 표시
  • 사운드 (브라우저 Audio API 사용)
  • 모바일 터치 지원
  • 점수 저장 (localStorage)
  • SRS 회전 시스템 (현실 테트리스처럼)

어떤 기능을 먼저 추가하고 싶으신가요?
또는 회전·충돌·하드드롭 중 특정 부분을 더 깊게 설명해 드릴까요? 😄

728x90

'ChatGPT 대답 > 소프트웨어 관련' 카테고리의 다른 글

Web Serial API  (0) 2026.02.04
C# / Node.js / ESP32에서 MQ를 실제로 쓰는 방법  (0) 2026.02.03
MQ(Message Queue) #1  (0) 2026.01.30

+ Recent posts