질문 :
자바스크립트로 테트리스 만들어줘
실행 화면 :

답변 :
좋아요! 이번에는 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>
사용법
- 위 코드를
tetris.html로 저장 - 더블클릭해서 브라우저에서 열기
- 조작키
← → / 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 |
