create-ax-project 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/create.js +97 -40
- package/package.json +5 -2
- package/template/.claude/CLAUDE.md +0 -7
- package/template/.claude/commands/CLAUDE.md +0 -0
- package/template/.claude/hooks/CLAUDE.md +0 -0
- package/template/.claude/skills/ai-collaboration/CLAUDE.md +0 -0
- package/template/.claude/skills/auto-checkpoint/CLAUDE.md +0 -0
- package/template/.claude/skills/context-compression/CLAUDE.md +0 -0
- package/template/.claude/skills/context-compression/prompts/CLAUDE.md +0 -0
- package/template/.claude/skills/output-validator/CLAUDE.md +0 -0
- package/template/.claude/skills/smart-handoff/CLAUDE.md +0 -0
- package/template/.claude/skills/stage-transition/CLAUDE.md +0 -0
- package/template/.claude/skills/stage-transition/prompts/CLAUDE.md +0 -0
- package/template/config/CLAUDE.md +0 -0
- package/template/scripts/CLAUDE.md +0 -0
- package/template/stages/01-brainstorm/HANDOFF.md +0 -110
- package/template/stages/01-brainstorm/inputs/CLAUDE.md +0 -0
- package/template/stages/01-brainstorm/inputs/project_brief.md +0 -40
- package/template/stages/01-brainstorm/outputs/CLAUDE.md +0 -0
- package/template/stages/01-brainstorm/outputs/ideas.md +0 -159
- package/template/stages/01-brainstorm/outputs/requirements_analysis.md +0 -222
- package/template/stages/01-brainstorm/prompts/CLAUDE.md +0 -0
- package/template/stages/01-brainstorm/templates/CLAUDE.md +0 -0
- package/template/stages/02-research/HANDOFF.md +0 -158
- package/template/stages/02-research/outputs/CLAUDE.md +0 -0
- package/template/stages/02-research/outputs/feasibility_report.md +0 -176
- package/template/stages/02-research/outputs/tech_research.md +0 -403
- package/template/stages/02-research/prompts/CLAUDE.md +0 -0
- package/template/stages/03-planning/HANDOFF.md +0 -168
- package/template/stages/03-planning/outputs/CLAUDE.md +0 -0
- package/template/stages/03-planning/outputs/architecture.md +0 -400
- package/template/stages/03-planning/outputs/implementation.yaml +0 -209
- package/template/stages/03-planning/outputs/project_plan.md +0 -204
- package/template/stages/03-planning/outputs/tech_stack.md +0 -176
- package/template/stages/03-planning/prompts/CLAUDE.md +0 -0
- package/template/stages/04-ui-ux/HANDOFF.md +0 -165
- package/template/stages/04-ui-ux/outputs/CLAUDE.md +0 -0
- package/template/stages/04-ui-ux/outputs/design_system.md +0 -449
- package/template/stages/04-ui-ux/outputs/user_flows.md +0 -321
- package/template/stages/04-ui-ux/outputs/wireframes.md +0 -241
- package/template/stages/04-ui-ux/prompts/CLAUDE.md +0 -0
- package/template/stages/05-task-management/HANDOFF.md +0 -187
- package/template/stages/05-task-management/outputs/CLAUDE.md +0 -0
- package/template/stages/05-task-management/outputs/milestones.md +0 -253
- package/template/stages/05-task-management/outputs/sprint_plan.md +0 -203
- package/template/stages/05-task-management/outputs/tasks.md +0 -402
- package/template/stages/05-task-management/prompts/CLAUDE.md +0 -0
- package/template/stages/05-task-management/templates/CLAUDE.md +0 -0
- package/template/stages/06-implementation/HANDOFF.md +0 -184
- package/template/stages/06-implementation/prompts/CLAUDE.md +0 -0
- package/template/stages/07-refactoring/HANDOFF.md +0 -82
- package/template/stages/07-refactoring/outputs/refactoring_report.md +0 -102
- package/template/stages/07-refactoring/prompts/CLAUDE.md +0 -0
- package/template/stages/08-qa/HANDOFF.md +0 -114
- package/template/stages/08-qa/outputs/qa_report.md +0 -138
- package/template/stages/08-qa/prompts/CLAUDE.md +0 -0
- package/template/stages/09-testing/HANDOFF.md +0 -118
- package/template/stages/09-testing/outputs/test_report.md +0 -146
- package/template/stages/09-testing/prompts/CLAUDE.md +0 -0
- package/template/stages/10-deployment/HANDOFF.md +0 -141
- package/template/stages/10-deployment/prompts/CLAUDE.md +0 -0
- package/template/stages/10-deployment/templates/CLAUDE.md +0 -0
- package/template/state/templates/CLAUDE.md +0 -0
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
# 🔬 Snake Game - 기술 리서치
|
|
2
|
-
|
|
3
|
-
> 생성일: 2026-01-21
|
|
4
|
-
> 스테이지: 02-research
|
|
5
|
-
> 프로젝트: snake-game
|
|
6
|
-
> AI 도구: Claude + Exa MCP
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## 📋 조사 항목 요약
|
|
11
|
-
|
|
12
|
-
| 항목 | 권장 기술 | 근거 |
|
|
13
|
-
|------|----------|------|
|
|
14
|
-
| 렌더링 | Canvas API | 게임 성능, 60fps 유지 |
|
|
15
|
-
| 게임 루프 | requestAnimationFrame | 브라우저 최적화, 탭 비활성화 처리 |
|
|
16
|
-
| 상태 관리 | useReducer + useRef | 복잡한 게임 상태, 리렌더링 최소화 |
|
|
17
|
-
| 모바일 조작 | Custom useSwipe Hook | 터치 이벤트 직접 처리 |
|
|
18
|
-
| 하이스코어 | localStorage | 백엔드 불필요, 간단한 구현 |
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## 1. Canvas 게임 루프 구현
|
|
23
|
-
|
|
24
|
-
### 1.1 requestAnimationFrame 기반 게임 루프
|
|
25
|
-
|
|
26
|
-
**핵심 패턴** (Exa 조사 결과):
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
// useGameLoop.ts
|
|
30
|
-
const useGameLoop = (callback: (deltaTime: number) => void) => {
|
|
31
|
-
const requestRef = useRef<number>();
|
|
32
|
-
const previousTimeRef = useRef<number>();
|
|
33
|
-
|
|
34
|
-
const animate = (time: number) => {
|
|
35
|
-
if (previousTimeRef.current !== undefined) {
|
|
36
|
-
const deltaTime = time - previousTimeRef.current;
|
|
37
|
-
callback(deltaTime);
|
|
38
|
-
}
|
|
39
|
-
previousTimeRef.current = time;
|
|
40
|
-
requestRef.current = requestAnimationFrame(animate);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
requestRef.current = requestAnimationFrame(animate);
|
|
45
|
-
return () => {
|
|
46
|
-
if (requestRef.current) {
|
|
47
|
-
cancelAnimationFrame(requestRef.current);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
}, []);
|
|
51
|
-
};
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### 1.2 setInterval vs requestAnimationFrame 비교
|
|
55
|
-
|
|
56
|
-
| 특성 | setInterval | requestAnimationFrame |
|
|
57
|
-
|------|-------------|----------------------|
|
|
58
|
-
| **정확도** | 낮음 (지연 발생) | 높음 (브라우저 최적화) |
|
|
59
|
-
| **탭 비활성화** | 계속 실행 (리소스 낭비) | 자동 일시정지 |
|
|
60
|
-
| **프레임레이트** | 고정 (부정확) | 디스플레이 동기화 (60fps) |
|
|
61
|
-
| **성능** | 보통 | 최적화됨 |
|
|
62
|
-
|
|
63
|
-
**결론**: requestAnimationFrame 사용 권장
|
|
64
|
-
|
|
65
|
-
### 1.3 하이브리드 게임 루프 (고정 타임스텝)
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
// 물리 시뮬레이션과 렌더링 분리
|
|
69
|
-
const TICK_RATE = 1000 / 60; // 60 TPS
|
|
70
|
-
let accumulator = 0;
|
|
71
|
-
|
|
72
|
-
const gameLoop = (currentTime: number) => {
|
|
73
|
-
const deltaTime = currentTime - lastTime;
|
|
74
|
-
accumulator += Math.min(deltaTime, 100); // 프레임 스킵 제한
|
|
75
|
-
|
|
76
|
-
while (accumulator >= TICK_RATE) {
|
|
77
|
-
updateGameState(); // 고정 타임스텝 업데이트
|
|
78
|
-
accumulator -= TICK_RATE;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
render(); // 가변 렌더링
|
|
82
|
-
requestAnimationFrame(gameLoop);
|
|
83
|
-
};
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
## 2. React + Canvas 통합 패턴
|
|
89
|
-
|
|
90
|
-
### 2.1 useRef로 Canvas 접근
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
const GameBoard: React.FC = () => {
|
|
94
|
-
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
95
|
-
|
|
96
|
-
useEffect(() => {
|
|
97
|
-
const canvas = canvasRef.current;
|
|
98
|
-
if (!canvas) return;
|
|
99
|
-
|
|
100
|
-
const ctx = canvas.getContext('2d');
|
|
101
|
-
if (!ctx) return;
|
|
102
|
-
|
|
103
|
-
// 게임 렌더링 로직
|
|
104
|
-
const draw = () => {
|
|
105
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
106
|
-
// ... 렌더링
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
draw();
|
|
110
|
-
}, [gameState]);
|
|
111
|
-
|
|
112
|
-
return <canvas ref={canvasRef} width={400} height={400} />;
|
|
113
|
-
};
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### 2.2 리렌더링 최소화 전략
|
|
117
|
-
|
|
118
|
-
**문제**: React setState가 너무 자주 호출되면 성능 저하
|
|
119
|
-
|
|
120
|
-
**해결책**:
|
|
121
|
-
1. **useRef로 게임 상태 관리** - 리렌더링 없이 상태 업데이트
|
|
122
|
-
2. **Canvas 직접 업데이트** - React DOM 업데이트 우회
|
|
123
|
-
3. **requestAnimationFrame 내에서 렌더링** - 효율적인 드로잉
|
|
124
|
-
|
|
125
|
-
```typescript
|
|
126
|
-
// 게임 상태는 useRef로 관리
|
|
127
|
-
const gameStateRef = useRef<GameState>({
|
|
128
|
-
snake: [{x: 10, y: 10}],
|
|
129
|
-
food: {x: 5, y: 5},
|
|
130
|
-
direction: 'RIGHT',
|
|
131
|
-
score: 0
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// UI에 표시할 점수만 useState로 관리
|
|
135
|
-
const [displayScore, setDisplayScore] = useState(0);
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
## 3. 모바일 터치 조작 구현
|
|
141
|
-
|
|
142
|
-
### 3.1 useSwipe 커스텀 훅
|
|
143
|
-
|
|
144
|
-
**Exa 조사 결과 기반 구현**:
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
interface SwipeHandlers {
|
|
148
|
-
left?: () => void;
|
|
149
|
-
right?: () => void;
|
|
150
|
-
up?: () => void;
|
|
151
|
-
down?: () => void;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const useSwipe = (handlers: SwipeHandlers) => {
|
|
155
|
-
const touchCoordsRef = useRef({
|
|
156
|
-
touchStart: { x: 0, y: 0, time: Date.now() }
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
useEffect(() => {
|
|
160
|
-
const handleTouchStart = (e: TouchEvent) => {
|
|
161
|
-
touchCoordsRef.current.touchStart = {
|
|
162
|
-
x: e.targetTouches[0].clientX,
|
|
163
|
-
y: e.targetTouches[0].clientY,
|
|
164
|
-
time: Date.now()
|
|
165
|
-
};
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const handleTouchEnd = (e: TouchEvent) => {
|
|
169
|
-
const threshold = 50; // 최소 스와이프 거리
|
|
170
|
-
const maxTime = 500; // 최대 스와이프 시간 (ms)
|
|
171
|
-
|
|
172
|
-
const { touchStart } = touchCoordsRef.current;
|
|
173
|
-
const touchEnd = {
|
|
174
|
-
x: e.changedTouches[0].clientX,
|
|
175
|
-
y: e.changedTouches[0].clientY
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
const elapsed = Date.now() - touchStart.time;
|
|
179
|
-
if (elapsed > maxTime) return;
|
|
180
|
-
|
|
181
|
-
const xDiff = touchStart.x - touchEnd.x;
|
|
182
|
-
const yDiff = touchStart.y - touchEnd.y;
|
|
183
|
-
|
|
184
|
-
if (Math.abs(xDiff) > Math.abs(yDiff)) {
|
|
185
|
-
// 수평 스와이프
|
|
186
|
-
if (Math.abs(xDiff) > threshold) {
|
|
187
|
-
xDiff > 0 ? handlers.left?.() : handlers.right?.();
|
|
188
|
-
}
|
|
189
|
-
} else {
|
|
190
|
-
// 수직 스와이프
|
|
191
|
-
if (Math.abs(yDiff) > threshold) {
|
|
192
|
-
yDiff > 0 ? handlers.up?.() : handlers.down?.();
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
window.addEventListener('touchstart', handleTouchStart, { passive: true });
|
|
198
|
-
window.addEventListener('touchend', handleTouchEnd, { passive: true });
|
|
199
|
-
|
|
200
|
-
return () => {
|
|
201
|
-
window.removeEventListener('touchstart', handleTouchStart);
|
|
202
|
-
window.removeEventListener('touchend', handleTouchEnd);
|
|
203
|
-
};
|
|
204
|
-
}, [handlers]);
|
|
205
|
-
};
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### 3.2 모바일 방향 버튼 (대안)
|
|
209
|
-
|
|
210
|
-
```typescript
|
|
211
|
-
const MobileControls: React.FC<{ onDirection: (dir: Direction) => void }> = ({ onDirection }) => (
|
|
212
|
-
<div className="mobile-controls">
|
|
213
|
-
<button onTouchStart={() => onDirection('UP')}>↑</button>
|
|
214
|
-
<div>
|
|
215
|
-
<button onTouchStart={() => onDirection('LEFT')}>←</button>
|
|
216
|
-
<button onTouchStart={() => onDirection('RIGHT')}>→</button>
|
|
217
|
-
</div>
|
|
218
|
-
<button onTouchStart={() => onDirection('DOWN')}>↓</button>
|
|
219
|
-
</div>
|
|
220
|
-
);
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
---
|
|
224
|
-
|
|
225
|
-
## 4. 충돌 감지 알고리즘
|
|
226
|
-
|
|
227
|
-
### 4.1 벽 충돌 (O(1))
|
|
228
|
-
|
|
229
|
-
```typescript
|
|
230
|
-
const checkWallCollision = (head: Position, boardSize: number): boolean => {
|
|
231
|
-
return head.x < 0 || head.x >= boardSize || head.y < 0 || head.y >= boardSize;
|
|
232
|
-
};
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
### 4.2 자기 몸 충돌 (O(n))
|
|
236
|
-
|
|
237
|
-
```typescript
|
|
238
|
-
const checkSelfCollision = (head: Position, body: Position[]): boolean => {
|
|
239
|
-
// body[0]은 머리이므로 1부터 시작
|
|
240
|
-
return body.slice(1).some(segment =>
|
|
241
|
-
segment.x === head.x && segment.y === head.y
|
|
242
|
-
);
|
|
243
|
-
};
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### 4.3 먹이 충돌 (O(1))
|
|
247
|
-
|
|
248
|
-
```typescript
|
|
249
|
-
const checkFoodCollision = (head: Position, food: Position): boolean => {
|
|
250
|
-
return head.x === food.x && head.y === food.y;
|
|
251
|
-
};
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
## 5. 상태 관리 구조
|
|
257
|
-
|
|
258
|
-
### 5.1 게임 상태 타입 정의
|
|
259
|
-
|
|
260
|
-
```typescript
|
|
261
|
-
// types/game.ts
|
|
262
|
-
type Direction = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT';
|
|
263
|
-
type GameStatus = 'idle' | 'playing' | 'paused' | 'gameover';
|
|
264
|
-
|
|
265
|
-
interface Position {
|
|
266
|
-
x: number;
|
|
267
|
-
y: number;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
interface GameState {
|
|
271
|
-
snake: Position[];
|
|
272
|
-
food: Position;
|
|
273
|
-
direction: Direction;
|
|
274
|
-
nextDirection: Direction; // 입력 버퍼
|
|
275
|
-
score: number;
|
|
276
|
-
level: number;
|
|
277
|
-
status: GameStatus;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
interface GameConfig {
|
|
281
|
-
boardSize: number;
|
|
282
|
-
cellSize: number;
|
|
283
|
-
initialSpeed: number;
|
|
284
|
-
speedIncrement: number;
|
|
285
|
-
}
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
### 5.2 useReducer 기반 상태 관리
|
|
289
|
-
|
|
290
|
-
```typescript
|
|
291
|
-
type GameAction =
|
|
292
|
-
| { type: 'START_GAME' }
|
|
293
|
-
| { type: 'PAUSE_GAME' }
|
|
294
|
-
| { type: 'RESUME_GAME' }
|
|
295
|
-
| { type: 'GAME_OVER' }
|
|
296
|
-
| { type: 'SET_DIRECTION'; payload: Direction }
|
|
297
|
-
| { type: 'MOVE_SNAKE' }
|
|
298
|
-
| { type: 'EAT_FOOD' }
|
|
299
|
-
| { type: 'RESET_GAME' };
|
|
300
|
-
|
|
301
|
-
const gameReducer = (state: GameState, action: GameAction): GameState => {
|
|
302
|
-
switch (action.type) {
|
|
303
|
-
case 'START_GAME':
|
|
304
|
-
return { ...state, status: 'playing' };
|
|
305
|
-
case 'SET_DIRECTION':
|
|
306
|
-
// 반대 방향 이동 방지
|
|
307
|
-
if (!isOppositeDirection(state.direction, action.payload)) {
|
|
308
|
-
return { ...state, nextDirection: action.payload };
|
|
309
|
-
}
|
|
310
|
-
return state;
|
|
311
|
-
case 'MOVE_SNAKE':
|
|
312
|
-
// 뱀 이동 로직
|
|
313
|
-
return moveSnake(state);
|
|
314
|
-
// ... 기타 액션
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
---
|
|
320
|
-
|
|
321
|
-
## 6. localStorage 하이스코어
|
|
322
|
-
|
|
323
|
-
### 6.1 저장 및 로드
|
|
324
|
-
|
|
325
|
-
```typescript
|
|
326
|
-
const HIGH_SCORE_KEY = 'snake_game_high_score';
|
|
327
|
-
|
|
328
|
-
const saveHighScore = (score: number): void => {
|
|
329
|
-
const currentHigh = getHighScore();
|
|
330
|
-
if (score > currentHigh) {
|
|
331
|
-
localStorage.setItem(HIGH_SCORE_KEY, score.toString());
|
|
332
|
-
}
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
const getHighScore = (): number => {
|
|
336
|
-
const stored = localStorage.getItem(HIGH_SCORE_KEY);
|
|
337
|
-
return stored ? parseInt(stored, 10) : 0;
|
|
338
|
-
};
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
### 6.2 커스텀 훅
|
|
342
|
-
|
|
343
|
-
```typescript
|
|
344
|
-
const useHighScore = () => {
|
|
345
|
-
const [highScore, setHighScore] = useState(() => getHighScore());
|
|
346
|
-
|
|
347
|
-
const updateHighScore = useCallback((score: number) => {
|
|
348
|
-
if (score > highScore) {
|
|
349
|
-
setHighScore(score);
|
|
350
|
-
saveHighScore(score);
|
|
351
|
-
}
|
|
352
|
-
}, [highScore]);
|
|
353
|
-
|
|
354
|
-
return { highScore, updateHighScore };
|
|
355
|
-
};
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
---
|
|
359
|
-
|
|
360
|
-
## 7. 성능 최적화 팁
|
|
361
|
-
|
|
362
|
-
### 7.1 Canvas 최적화
|
|
363
|
-
|
|
364
|
-
1. **오프스크린 캔버스**: 복잡한 요소 미리 렌더링
|
|
365
|
-
2. **부분 리드로잉**: 변경된 부분만 다시 그리기
|
|
366
|
-
3. **이미지 스프라이트**: 여러 이미지 하나로 합치기
|
|
367
|
-
|
|
368
|
-
### 7.2 React 최적화
|
|
369
|
-
|
|
370
|
-
1. **useCallback/useMemo**: 불필요한 재생성 방지
|
|
371
|
-
2. **React.memo**: 순수 컴포넌트 메모이제이션
|
|
372
|
-
3. **게임 로직 분리**: Canvas 업데이트는 React 외부에서
|
|
373
|
-
|
|
374
|
-
---
|
|
375
|
-
|
|
376
|
-
## 📚 참고 자료
|
|
377
|
-
|
|
378
|
-
### GitHub 오픈소스 프로젝트
|
|
379
|
-
- [gimnathperera/snake-loop](https://github.com/gimnathperera/snake-loop) - React + RTK Query
|
|
380
|
-
- [markkaylor/react-snake-ts](https://github.com/markkaylor/react-snake-ts) - TypeScript 구현
|
|
381
|
-
- [v662-coder/SnakeGame](https://github.com/v662-coder/SnakeGame) - Canvas 기반
|
|
382
|
-
|
|
383
|
-
### 기술 문서
|
|
384
|
-
- [30-seconds-of-code: useRequestAnimationFrame](https://github.com/Chalarangelo/30-seconds-of-code)
|
|
385
|
-
- [react-use: useRafLoop](https://github.com/streamich/react-use)
|
|
386
|
-
- [beautiful-react-hooks: useRequestAnimationFrame](https://github.com/antonioru/beautiful-react-hooks)
|
|
387
|
-
|
|
388
|
-
### 튜토리얼
|
|
389
|
-
- [Learn Advance React Hooks by Building Snake Game](https://javascript.plainenglish.io/learn-advance-react-hooks-by-building-snake-game)
|
|
390
|
-
- [Build Your First Snake Game with TypeScript React](https://javascript.plainenglish.io/build-your-first-snake-game-with-typescript-react)
|
|
391
|
-
- [Create Dead-Simple Canvas Animations in React](https://spin.atomicobject.com/animations-react)
|
|
392
|
-
|
|
393
|
-
---
|
|
394
|
-
|
|
395
|
-
## ✅ 리서치 완료 체크리스트
|
|
396
|
-
|
|
397
|
-
- [x] Canvas 게임 루프 구현 방법 조사
|
|
398
|
-
- [x] React + Canvas 통합 패턴 분석
|
|
399
|
-
- [x] 모바일 터치 이벤트 처리 조사
|
|
400
|
-
- [x] 충돌 감지 알고리즘 정리
|
|
401
|
-
- [x] localStorage 하이스코어 구현 방법
|
|
402
|
-
- [x] 성능 최적화 기법 조사
|
|
403
|
-
- [x] 오픈소스 프로젝트 사례 수집
|
|
File without changes
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
# HANDOFF: 03-planning → 04-ui-ux
|
|
2
|
-
|
|
3
|
-
> 생성일: 2026-01-21
|
|
4
|
-
> 프로젝트: snake-game
|
|
5
|
-
> 현재 스테이지: 03-planning (완료)
|
|
6
|
-
> 다음 스테이지: 04-ui-ux
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## ✅ 완료된 작업
|
|
11
|
-
|
|
12
|
-
- [x] 시스템 아키텍처 설계 (컴포넌트 구조, 데이터 흐름)
|
|
13
|
-
- [x] 기술 스택 최종 결정 (React 18 + TypeScript 5 + Vite 5 + Canvas API)
|
|
14
|
-
- [x] 커스텀 훅 설계 (useGameLoop, useKeyboard, useSwipe, useHighScore)
|
|
15
|
-
- [x] 타입 정의 (Point, Direction, GameStatus, GameState, GameAction)
|
|
16
|
-
- [x] 게임 설정 상수 정의 (GAME_CONFIG, COLORS, DIRECTION_VECTORS)
|
|
17
|
-
- [x] 프로젝트 계획 수립 (마일스톤, 스프린트 계획)
|
|
18
|
-
- [x] 구현 규칙 정의 (implementation.yaml)
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## 📋 핵심 아키텍처 결정사항
|
|
23
|
-
|
|
24
|
-
### 1. 컴포넌트 구조 (하이브리드)
|
|
25
|
-
|
|
26
|
-
```
|
|
27
|
-
App
|
|
28
|
-
└── GameEngine (상태 관리, 이벤트 처리)
|
|
29
|
-
├── CanvasLayer (Canvas 렌더링)
|
|
30
|
-
└── UIOverlay (React UI)
|
|
31
|
-
├── ScoreBoard
|
|
32
|
-
├── GameOverModal
|
|
33
|
-
├── StartScreen
|
|
34
|
-
└── MobileControls
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### 2. 데이터 흐름
|
|
38
|
-
|
|
39
|
-
- **useReducer**: UI 리렌더링 필요한 상태 (score, status)
|
|
40
|
-
- **useRef**: 빠른 업데이트 (direction buffer, animationFrameId)
|
|
41
|
-
- **Canvas 직접 조작**: 게임 렌더링 (리렌더링 최소화)
|
|
42
|
-
|
|
43
|
-
### 3. 게임 루프
|
|
44
|
-
|
|
45
|
-
```typescript
|
|
46
|
-
requestAnimationFrame 기반
|
|
47
|
-
├── deltaTime 계산
|
|
48
|
-
├── dispatch({ type: 'TICK' })
|
|
49
|
-
└── Canvas.draw()
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## 📁 생성된 산출물
|
|
55
|
-
|
|
56
|
-
| 파일 | 설명 |
|
|
57
|
-
|------|------|
|
|
58
|
-
| `outputs/architecture.md` | 시스템 아키텍처 상세 설계 |
|
|
59
|
-
| `outputs/tech_stack.md` | 기술 스택 결정 및 근거 |
|
|
60
|
-
| `outputs/project_plan.md` | 프로젝트 계획 및 마일스톤 |
|
|
61
|
-
| `outputs/implementation.yaml` | 구현 규칙 설정 |
|
|
62
|
-
| `HANDOFF.md` | 이 문서 |
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
## 🎨 UI/UX 스테이지 입력 정보
|
|
67
|
-
|
|
68
|
-
### 화면 구성 요소
|
|
69
|
-
|
|
70
|
-
| 화면 | 구성 요소 |
|
|
71
|
-
|------|----------|
|
|
72
|
-
| **시작 화면** | 타이틀, 시작 버튼, 최고 점수 |
|
|
73
|
-
| **게임 화면** | Canvas(뱀/먹이/그리드), 점수판 |
|
|
74
|
-
| **일시정지** | 오버레이, 재개/재시작 버튼 |
|
|
75
|
-
| **게임 오버** | 모달, 최종 점수, 최고 점수, 재시작 버튼 |
|
|
76
|
-
| **모바일** | 방향 버튼 (상하좌우) |
|
|
77
|
-
|
|
78
|
-
### 색상 시스템 (확정)
|
|
79
|
-
|
|
80
|
-
```yaml
|
|
81
|
-
background: "#1a1a2e" # 다크 배경
|
|
82
|
-
grid: "#16213e" # 그리드 라인
|
|
83
|
-
snake: "#00ff88" # 뱀 몸통 (네온 그린)
|
|
84
|
-
snake_head: "#00cc6a" # 뱀 머리 (진한 그린)
|
|
85
|
-
food: "#ff6b6b" # 먹이 (레드)
|
|
86
|
-
text: "#ffffff" # 텍스트 (화이트)
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### 반응형 기준
|
|
90
|
-
|
|
91
|
-
| 브레이크포인트 | 대상 |
|
|
92
|
-
|--------------|------|
|
|
93
|
-
| < 480px | 모바일 (터치 컨트롤 활성화) |
|
|
94
|
-
| 480px - 768px | 태블릿 |
|
|
95
|
-
| > 768px | 데스크톱 |
|
|
96
|
-
|
|
97
|
-
### 게임 보드 크기
|
|
98
|
-
|
|
99
|
-
```yaml
|
|
100
|
-
boardSize: 20 # 20x20 그리드
|
|
101
|
-
cellSize: 20px # 각 셀 20px
|
|
102
|
-
totalSize: 400x400px # 게임 보드 총 크기
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
---
|
|
106
|
-
|
|
107
|
-
## 🔜 04-ui-ux 스테이지 작업
|
|
108
|
-
|
|
109
|
-
### 필수 산출물
|
|
110
|
-
|
|
111
|
-
1. **wireframes.md**
|
|
112
|
-
- 시작 화면 와이어프레임
|
|
113
|
-
- 게임 화면 와이어프레임 (데스크톱/모바일)
|
|
114
|
-
- 게임 오버 모달 와이어프레임
|
|
115
|
-
|
|
116
|
-
2. **design-tokens.md**
|
|
117
|
-
- 색상 토큰
|
|
118
|
-
- 타이포그래피
|
|
119
|
-
- 간격 시스템
|
|
120
|
-
- 애니메이션 토큰
|
|
121
|
-
|
|
122
|
-
3. **HANDOFF.md**
|
|
123
|
-
|
|
124
|
-
### 고려 사항
|
|
125
|
-
|
|
126
|
-
- 레트로 게임 느낌 (네온 색상, 픽셀 폰트 고려)
|
|
127
|
-
- 미니멀한 UI (게임에 집중)
|
|
128
|
-
- 모바일 터치 영역 충분히 확보 (44x44px 최소)
|
|
129
|
-
- 접근성 (키보드 네비게이션, 색상 대비)
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## ⚠️ 제약 사항
|
|
134
|
-
|
|
135
|
-
1. **순수 React + Canvas**: 외부 게임 엔진 미사용
|
|
136
|
-
2. **함수형 컴포넌트**: Class 컴포넌트 미사용
|
|
137
|
-
3. **CSS Modules**: 스타일 스코프 격리
|
|
138
|
-
4. **60fps 목표**: Canvas 렌더링 최적화
|
|
139
|
-
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
## 📊 AI 호출 기록
|
|
143
|
-
|
|
144
|
-
| AI | 시간 | 도구 | 결과 | 상태 |
|
|
145
|
-
|----|------|------|------|------|
|
|
146
|
-
| Gemini | 12:25 | tmux wrapper | 아키텍처 설계 | ✅ |
|
|
147
|
-
| ClaudeCode | 12:30 | - | architecture.md 작성 | ✅ |
|
|
148
|
-
| ClaudeCode | 12:32 | - | tech_stack.md 작성 | ✅ |
|
|
149
|
-
| ClaudeCode | 12:35 | - | project_plan.md 작성 | ✅ |
|
|
150
|
-
| ClaudeCode | 12:36 | - | implementation.yaml 작성 | ✅ |
|
|
151
|
-
|
|
152
|
-
---
|
|
153
|
-
|
|
154
|
-
## 🚀 다음 단계
|
|
155
|
-
|
|
156
|
-
```bash
|
|
157
|
-
# 다음 스테이지 실행
|
|
158
|
-
/run-stage 04-ui-ux
|
|
159
|
-
|
|
160
|
-
# 또는
|
|
161
|
-
/ui-ux
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
---
|
|
165
|
-
|
|
166
|
-
**생성자**: ClaudeCode + Gemini
|
|
167
|
-
**검토자**: -
|
|
168
|
-
**승인**: 대기
|
|
File without changes
|