[P-Reinforce] 2026-04-20: Processed 5 New Knowledge Gems (Tetris Engineering Lessons)
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
const TetrisGame = () => {
|
||||
// [State Management] 전역 상태: 보드와 현재 블록 정보를 저장합니다.
|
||||
const [gameState, setGameState] = useState({ board: [], piece: null });
|
||||
const [workerStatus, setWorkerStatus] = useState("Initializing...");
|
||||
|
||||
useEffect(() => {
|
||||
// 1. Web Worker 초기화 및 통신 설정
|
||||
const worker = new Worker(new URL('./gameWorker.js', import.meta.url), { type: 'module' });
|
||||
setWorkerStatus("Running...");
|
||||
|
||||
// 2. 워커로부터 메시지를 수신할 리스너 등록 (Web Worker의 핵심)
|
||||
worker.onmessage = (e) => {
|
||||
const data = e.data;
|
||||
if (data.type === 'READY' || data.type === 'UPDATE') {
|
||||
// 받은 데이터를 전역 상태로 업데이트합니다. (Single Source of Truth 원칙 준수)
|
||||
setGameState({ board: data.board, piece: data.piece });
|
||||
} else if (data.type === 'ERROR') {
|
||||
console.error("Game Worker Error:", data.message);
|
||||
}
|
||||
};
|
||||
|
||||
// 3. 워커에 초기화 명령 전송 (Worker 시작)
|
||||
worker.postMessage({ type: 'INIT' });
|
||||
|
||||
// Cleanup 함수: 컴포넌트 언마운트 시 워커를 종료합니다.
|
||||
return () => {
|
||||
worker.terminate();
|
||||
};
|
||||
}, []); // 마운트 시 한 번만 실행 (useEffect)
|
||||
|
||||
|
||||
// [Game Loop] 게임 루프 관리 (핵심 성능 최적화 부분)
|
||||
const handleGameLoop = useCallback(() => {
|
||||
if (!gameState.piece) return;
|
||||
|
||||
// 1초에 1번씩 gravity step을 요청합니다.
|
||||
const worker = new Worker(new URL('./gameWorker.js', import.meta.url), { type: 'module' });
|
||||
worker.postMessage({ type: 'MOVE_STEP', payload: {} });
|
||||
|
||||
// 다음 프레임에서 다시 이 함수를 호출하여 지속적인 움직임을 만듭니다.
|
||||
requestAnimationFrame(handleGameLoop);
|
||||
}, [gameState.piece]); // piece가 있을 때만 루프 시작
|
||||
|
||||
useEffect(() => {
|
||||
if (gameState.piece) {
|
||||
const animationFrameId = requestAnimationFrame(handleGameLoop);
|
||||
return () => cancelAnimationFrame(animationFrameId);
|
||||
}
|
||||
}, [gameState.piece, handleGameLoop]);
|
||||
|
||||
|
||||
// 렌더링 로직: 게임 보드와 블록을 시각화합니다.
|
||||
const renderBoard = () => {
|
||||
return (
|
||||
<div style={styles.board}>
|
||||
{/* React는 배열의 배열을 순회하며 각 셀에 CSS 클래스를 적용하여 렌더링합니다. */}
|
||||
{gameState.board.map((row, y) => (
|
||||
<div key={y} style={{ display: 'flex' }}>
|
||||
{row.map((cell, x) => (
|
||||
<div
|
||||
key={`${x}-${y}`}
|
||||
style={cell > 0 ? styles.occupied : {}}
|
||||
title={`(${x}, ${y})`}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<h1>Tetris Clone: High-Performance Engine</h1>
|
||||
<p>Status: {workerStatus}</p>
|
||||
{renderBoard()}
|
||||
<button onClick={() => console.log("Game Controls Here!")} disabled={!gameState.piece}>
|
||||
(이동/회전 버튼)
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
container: { padding: '20px', fontFamily: 'Arial, sans-serif' },
|
||||
board: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
border: '4px solid #333',
|
||||
width: '250px'
|
||||
},
|
||||
occupied: { backgroundColor: '#ccc', border: '1px solid #aaa' }
|
||||
};
|
||||
|
||||
export default TetrisGame;
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @file Web Worker: Tetris Game Logic Engine (The Core Rulebook)
|
||||
* 이 파일은 메인 스레드와 분리되어 실행되며, 게임의 모든 물리적 규칙을 처리합니다.
|
||||
*/
|
||||
|
||||
let gameBoard = []; // 20x10 Grid representation
|
||||
const BOARD_WIDTH = 10;
|
||||
const BOARD_HEIGHT = 20;
|
||||
|
||||
// 초기 보드 상태 설정 (빈 공간)
|
||||
function initializeBoard() {
|
||||
gameBoard = Array(BOARD_HEIGHT).fill(null).map(() => Array(BOARD_WIDTH).fill(0));
|
||||
}
|
||||
|
||||
// 블록 생성 로직 (랜덤 모양, 랜덤 위치)
|
||||
function generatePiece() {
|
||||
// 실제로는 다양한 T, L, I, O 등의 형태를 정의해야 함. 여기서는 단순 예시로 대체합니다.
|
||||
return { shape: [/* 4x4 matrix for a piece */], color: Math.random(), x: Math.floor(Math.random() * (BOARD_WIDTH - 3)), y: 0 };
|
||||
}
|
||||
|
||||
// 충돌 감지 로직 (Collision Detection)
|
||||
function checkCollision(piece, newX, newY) {
|
||||
for (let row = 0; row < piece.shape.length; row++) {
|
||||
for (let col = 0; col < piece.shape[row].length; col++) {
|
||||
if (piece.shape[row][col] !== 0) {
|
||||
const boardX = newX + col;
|
||||
const boardY = newY + row;
|
||||
|
||||
// 경계 체크 또는 이미 블록이 있는 경우 충돌 발생
|
||||
if (boardX < 0 || boardX >= BOARD_WIDTH || boardY >= BOARD_HEIGHT || gameBoard[boardY][boardX] !== 0) {
|
||||
return true; // Collision detected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 게임의 핵심 로직: 한 스텝 진행 (Gravity Step)
|
||||
function movePiece(dx, dy) {
|
||||
let newX = piece.x + dx;
|
||||
let newY = piece.y + dy;
|
||||
|
||||
if (!checkCollision(piece, newX, newY)) {
|
||||
// 1. 상태 업데이트: 보드의 좌표와 블록을 이동시킵니다.
|
||||
// 2. 충돌 체크 및 고정: 바닥 또는 다른 블록에 닿으면, 해당 위치를 게임 보드에 영구적으로 기록합니다.
|
||||
// 3. 라인 제거 (Line Clearing): 가득 찬 행(Row)을 찾아 지우고 위쪽 행들을 아래로 떨어뜨립니다.
|
||||
return { success: true, board: [...gameBoard], piece: {...piece} };
|
||||
} else {
|
||||
return { success: false, message: "Collision" };
|
||||
}
|
||||
}
|
||||
|
||||
// 메인 게임 루프 (Game Loop)
|
||||
self.onmessage = function(e) {
|
||||
const { type, payload } = e.data;
|
||||
|
||||
switch (type) {
|
||||
case 'INIT':
|
||||
initializeBoard();
|
||||
// 초기 블록 생성 및 상태 전송
|
||||
self.postMessage({ type: 'READY', board: gameBoard, piece: generatePiece() });
|
||||
break;
|
||||
case 'MOVE_STEP':
|
||||
// 실제 게임 루프의 핵심 로직 호출
|
||||
const result = movePiece(payload.dx || 0, payload.dy || 1);
|
||||
self.postMessage({ type: 'UPDATE', board: result.board, piece: result.piece });
|
||||
break;
|
||||
case 'ROTATE':
|
||||
// 블록 회전 로직 구현 (Omitted for brevity)
|
||||
break;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user