[JavaScript] 프론트엔드만으로 구성된 TETRIS 배포하기
[개요 - 아이템 선정 이유]
프로젝트 배포를 경험해 보면서, "단일 서버 + 프론트엔드" 이 두가지만으로 완전히 작동하는 서비스를 만들어보고, 배포해 보고 싶었다.
그래서 선택한 프로젝트 주제가 다음과 같다.
"프론트엔드 파일만으로 구성된 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