카테고리 없음

[JavaScript] 프론트엔드만으로 구성된 TETRIS 배포하기

MoveForward 2025. 3. 4. 13:34

[개요 - 아이템 선정 이유]

프로젝트 배포를 경험해 보면서, "단일 서버 + 프론트엔드" 이 두가지만으로 완전히 작동하는 서비스를 만들어보고, 배포해 보고 싶었다.

 

그래서 선택한 프로젝트 주제가 다음과 같다.

 

"프론트엔드 파일만으로 구성된 TETRIS 배포" 이다. 

 

[명세서]

원하는 기능 / 조건은 다음과 같다.

 

  • 10 X 20 크기
  • 기능
    • 테트로미노 종류 (7가지) : I, O, T, L, J, S, Z
    • 테트로미노 이동 방식
      • 1. 좌우 1칸씩 이동 - 방향키 좌우
      • 2. Hard Drop - 'space bar'
    • 테트로미노 회전 방식
      • 시계 방향 회전 - 'Z' 키 , 방향키 상단
    • 테트로미노는 하단 방향에 바닥 OR 다른 블록을 만나면 쌓임(고정됨).
    • 테트로미노 생성 패턴
      • 1번째 ~ 7번째 테트로미노는 7종류가 무작위 순서로 무조건 한번씩 생성
      • 8번째 이후는 동일 테트로미노가 연속 등장하지 않고, 무작위로 생성
    • 점수 산정 방식
      • 한 줄이 가득차는 경우 : +100
      • 방향키 하단키를 눌러 블록을 내리는 경우 : +1
      • Hard Drop을 하는 경우 (space bar) : +30
 
[미리보는 플레이 스크린샷]

 

[프로젝트 파일 구성]

📦front-game
 ┣ 📜index.html
 ┣ 📜myscript.js
 ┗ 📜style.css
  • index.html - canvas 와 시작 버튼이 위치
  • myscript.js - TETRIS에 대한 모든 동작 (게임 + 점수)
  • style.css - css 파일

매우 간단한 구성이다.

 

 

[index.html 구성]

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SUPERMARUO</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="ganeContainer">
        <canvas id="gameCanvas"></canvas>
        <button id="startButton">게임 시작</button>
    </div>
    <script src="myscript.js"></script>
</body>
</html>

 

 

[style.css 구성]

body {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background: #222;
}
canvas {
  border: 2px solid white;
  background: black;
}


#gameContainer {
  position: relative;
  display: inline-block;
}

#gameCanvas {
  border: 1px solid black;
}

#startButton {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: 10px 20px;
  font-size: 16px;
  background-color: #4CAF50;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

#startButton:hover {
  background-color: #45a049;
}

 

 

[myscript.js 구성]

 

전체 파일 코드는 주소 메서드 설명 후 기입하겠다.

 

1.  테트리스 퍼즐 (테트로미노) 종류 7가지

 

퍼즐의 종류는 총 7가지로 ["I", "O", "T", "L", "J", "S", "Z"] 이다.

// ["I", "O", "T", "L", "J", "S", "Z"]
const T_COLORS = ["skyblue", "yellow", "purple", "orange", "blue", "green", "red"];

//퍼즐 종류 : 7가지
const TETROMINOS = [
  [[1, 1, 1, 1]], //I
  [[1, 1], [1, 1]], //O
  [[0, 1, 0], [1, 1, 1]], //T
  [[0, 0, 1], [1, 1, 1]], //L
  [[1, 0, 0], [1, 1, 1]], //J
  [[0, 1, 1], [1, 1, 0]], //S
  [[1, 1, 0], [0, 1, 1]] //Z
];

 

 

2. 테트로미노 생성 조건

- 첫 7개를 무작위로 생성하기 위해 "Queue"를 이용하여 구현한다.

- 8번째부터는 연속되지 않고, 무작위로 생성된다.

//시작시 7종류의 블럭을 모두 한번씩 사용하는 함수
function initializeQueue() {
  let numbers = [0, 1, 2, 3, 4, 5, 6];
  numbers.sort(() => Math.random() - 0.5); // 배열을 랜덤하게 섞기
  return [...numbers]; // 새로운 큐 반환
}

// 큐 초기화
let queue = initializeQueue();


let lastTetromino = null;

/**
 * 테트로미노 생성
 */
function createTetromino() {

  let randomNum;

  // 처음 7개의 블록을 큐에서 하나씩 빼고
  if (queue.length > 0) {
    randomNum = queue.shift(); // 첫 7개 블록을 차례대로 뽑음
  } else {
    do {
      // 8번째부터는 7개 종류 중 무작위로 선택
      randomNum = Math.floor(Math.random() * TETROMINOS.length);
    } while (randomNum === lastTetromino);
  }

  lastTetromino = randomNum; //현재 블럭을 기억해서 연속된 블럭 방지


  const currentPiece = {
    shape: TETROMINOS[randomNum], //랜덤 퍼즐 선택
    color: T_COLORS[randomNum], //퍼즐 색
    row: 0,
    col: 3
  };
  return currentPiece
}

 

 

3. 테트로미노의 충돌 여부 판단

//충돌 여부 확인
function isCollision() {
  const shape = currentTetromino.shape;
  for (let row = 0; row < shape.length; row++) {
    for (let col = 0; col < shape[row].length; col++) {
      if (shape[row][col]) {
        const boardX = currentTetromino.col + col;
        const boardY = currentTetromino.row + row;
        
        //맵 밖으로 나가있음 OR 기존 블럭과 겹쳐져있음 => 충돌!
        if (boardY >= ROWS || boardX < 0 || boardX >= COLS || board[boardY][boardX]) {
          return true;
        }
      }
    }
  }
  return false;
}

 

 

4. 테트로미노 고정!

블록의 하강의 역할을 수행하는 함수('자동 하강' , 'Hard Drop')가 바닥 OR 다른 블록과 "충돌"이 생기면, 해당 블록은 더이상 움직일 수 없기 때문에, 해당 블럭을 보드에 고정시키는 역할을 하는 메서드

//테트리미노 고정
function placeTetromino() {
  const shape = currentTetromino.shape;
  
  for (let row = 0; row < shape.length; row++) {
    for (let col = 0; col < shape[row].length; col++) {
      if (shape[row][col]) {
        board[currentTetromino.row + row][currentTetromino.col + col] = currentTetromino.color;
      }
    }
  }

  clearLines(); //한 줄이 채워졌다면 삭제
}

 

 

5. 테트로미노 회전

//테트리미노 회전
function rotateTetromino() {
  // 행렬을 시계방향 90도 회전
  const newShape = currentTetromino.shape[0].map((_, index) => currentTetromino.shape.map(row => row[index])).reverse();

  //회전 전 모양 저장 (백업) - 충돌시 원복
  const originalShape = currentTetromino.shape;
  currentTetromino.shape = newShape;

  //충돌 발생시 - 원복
  if (isCollision()) {
    currentTetromino.shape = originalShape;
  }
}

 

 

전체 코드

https://github.com/yashin20/tetris-frontend

 

GitHub - yashin20/tetris-frontend: Frontend 만으로 테트리스 배포하기

Frontend 만으로 테트리스 배포하기. Contribute to yashin20/tetris-frontend development by creating an account on GitHub.

github.com

 

 

<실행 해보기>

https://yashin20.github.io/tetris-frontend/

 

SUPERMARUO

 

yashin20.github.io