-
Notifications
You must be signed in to change notification settings - Fork 0
04 quest game ui
์์ฑ: ๋ฐ๋์ (pkdje) ยท 2026-06-02 ยท Week 2 ์์ ์์ ์ ์ถ๊ฐ๋ ๊ฒฐ์
๊ธฐ์กด 01-project-plan.md ์ DAG ์๊ฐํ๋ "๋ ธ๋ + ์ฃ์ง + ์ํ๋ณ ์" ์ ๊น๋ํ todo ํค์ด์๋ค. ์ฌ์ฉ์๊ฐ ํต์ฌ ๋ฉ์ปค๋์ฆ(์ ํ ์์ ์๋ฃ โ ํ์ ์์ ํด๊ธ)์ ๊ฒ์ ํ์คํธ ์งํ ๋๋์ผ๋ก ์ฌํด์ํ๊ณ ์ถ๋ค๊ณ ๊ฒฐ์ ํจ. ๋์์ ๊ธฐ์กด ๋ชจ๋ธ์ด ๋ค๋ฃจ์ง ์๋ "์ ํ๊ด๊ณ๊ฐ ์๋ ๋ ๋ฆฝ ํ์คํฌ"์ ๋ถ๋ฅ๋ ๋ช ํํ ํ๋ค.
์ด ๋ฌธ์๋ ๊ทธ ๊ฒฐ์ ์ ๊ตฌ์ฒดํํ๋ค. ๋ฐ์ดํฐ ๋ชจ๋ธ์ ๊ฑฐ์ ๊ทธ๋๋ก ๋๊ณ , ์๊ฐ/์ธํฐ๋์ ๋ ์ด์ด๋ฅผ ํ์คํธ ํค์ผ๋ก ํต์ผํ๋ค.
| ํญ๋ชฉ | ๊ฒฐ์ |
|---|---|
| ๊ฒ์ด๋ฏธํผ์ผ์ด์ ๊ฐ๋ | C โ ์ค๊ฐ (ํ์คํธ ์นด๋ ํค). ์นด๋ ๋์์ธ + ๋ผ๋ฒจ(REWARD, LOCKED, CLEARED) + ํด๊ธ ์ ๋๋ฉ์ด์ + QUEST LINE ์งํ๋ฅ . |
| ๋ ๋ฆฝ ํ์คํฌ ๋ถ๋ฅ | B + D. ์ข์ธก ํจ๋ ํ๋จ์ ๋ณ๋ "์ฌ์ด๋ ํ์คํธ" ์น์ . ์ ํ์คํฌ ์์ฑ ์ prerequisite ๋น์ฐ๋ฉด ์๋ ๋ถ๋ฅ. |
| ํ์คํธ ๋ผ์ธ ์ ์ | DAG ์ ์ฐ๊ฒฐ ์ปดํฌ๋ํธ = 1 ํ์คํธ ๋ผ์ธ. ํฌ๊ธฐ 1 = ์ฌ์ด๋ ํ์คํธ. |
| REWARD ํ ์คํธ | DAG ๊ตฌ์กฐ์์ ์๋ ์์ฑ (์ง์ ํ์ ๋ ธ๋ ๊ธฐ๋ฐ). ์ฌ์ฉ์ ์ ๋ ฅ ์์. |
| ์์ด์ฝ |
์ ํ emoji ํ๋ (icon?: string). ๊ธฐ๋ณธ โก. ์ ํ์คํฌ/ํธ์ง ์ picker. |
| ์ถ์ฒ ํจ๋ ํตํฉ | A โ ์ถ์ฒ ํจ๋์ "โถ NEXT QUEST" ๋ก ๋ฆฌ์คํจ. ์ ์ฒด UI ํค ํต์ผ. |
| ์ค์ฝํ | Week 3 ์์ ๋ผ์. #16(๊ธฐ์กด ๋ ธ๋ polish) ํก์ + ์ ๊ท #21โ#23 ์ถ๊ฐ. |
type Task = {
// ... ๊ธฐ์กด ํ๋ ๊ทธ๋๋ก
icon?: string // ์ถ๊ฐ. ์ด๋ชจ์ง 1์. ๋ฏธ์ง์ ์ ๋ ๋ ์ โก fallback.
}- ๋ค๋ฅธ ํ๋๋ ๋ณ๋ ์์.
- ๋ชจ๋ ํ์คํธ ๋ผ์ธ/REWARD ์ ๋ณด๋ ์ ์ฅํ์ง ์๊ณ DAG ๊ตฌ์กฐ์์ ํ์ ๊ณ์ฐ.
- ๋ง์ด๊ทธ๋ ์ด์ ๋ถํ์ (๊ธฐ์กด task ๋ค์ icon ์์ด๋ โก ๋ก ๋ ๋).
๋ชจ๋ ์์ ํจ์. TDD ๋์.
// 4.1 ์ฐ๊ฒฐ ์ปดํฌ๋ํธ (Union-Find ๋๋ BFS)
function connectedComponents(tasks: Task[]): Component[]
// Component { id: string, taskIds: TaskId[] }
// ์ฌ์ด์ฆ 1 = ์ฌ์ด๋ ํ์คํธ.
// 4.2 ์ปดํฌ๋ํธ ์ง์ฒ๋
function questLineProgress(componentId, components, tasks): { done: number, total: number }
// 4.3 REWARD ํ
์คํธ ์๋ ์์ฑ
function rewardText(task: Task, tasks: Task[]): string
// ๊ท์น:
// ์ง์ ํ์ 1๊ฐ โ "{์ด๋ฆ} ํด๊ธ"
// ์ง์ ํ์ 2~3๊ฐ โ "{์ด๋ฆ1}, {์ด๋ฆ2} ํด๊ธ"
// ์ง์ ํ์ 4๊ฐ+ โ "{N}๊ฐ ์์
ํด๊ธ"
// ํ์ ์์ + ์ปดํฌ๋ํธ ํฌ๊ธฐ > 1 โ "๋ฉ์ธ ํ์คํธ X/Y ์๋ฃ"
// ํ์ ์์ + ์ปดํฌ๋ํธ ํฌ๊ธฐ == 1 โ "์ฌ์ด๋ ํ์คํธ ์๋ฃ"
// 4.4 ํด๊ธ ๊ฐ์ง (์ ๋๋ฉ์ด์
ํธ๋ฆฌ๊ฑฐ)
function detectUnlocks(prev: Task[], next: Task[]): TaskId[]
// ์ด์ ์ ๋งํ(prereq ๋ฏธ์), ์ง๊ธ์ ready ์ธ task ๋ค.
// ๋ค์ ๋ ๋ ์ฌ์ดํด์์ 0.9์ด ํ์ค ์ ๋๋ฉ์ด์
.๋ณต์ก๋: ๋ชจ๋ O(V+E). ์๋ฐฑ ๋ ธ๋ ๊ธฐ์ค ์ฆ์ ๋๋จ.
React Flow ์ Custom Node ๋ก ๊ตฌํ. ๊ธฐ์กด ๋จ์ ๋ฐ์ค ๋ ธ๋ ๊ต์ฒด.
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐จ priority โ
โ
โ
โ
โ
โ ์์ด์ฝ + ์ฐ์ ์์
โ ๋์์ธ โ ์ ๋ชฉ (๊ตต๊ฒ)
โ D-3 ยท score 7.4 โ ๋ง๊ฐ + (์ถ์ฒ ํ๋ณด์ผ ๋) ์ ์
โ โโโโโโโโโโโโโโโโโ โ
โ โท REWARD โ
โ ํ๋ก ํธ, ๋ฐฑ์๋ ํด๊ธ โ ์๋ ์์ฑ
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
์ํ๋ณ ๋ณํ:
| ์ํ | ์๊ฐ |
|---|---|
todo (ready) |
ํฐ ๋ฐฐ๊ฒฝ, ๊ธฐ๋ณธ ํ ๋๋ฆฌ |
todo (locked โ prereq ๋ฏธ์) |
ํ์ ๋ฐฐ๊ฒฝ, opacity 0.6, ์ข์๋จ ๐ LOCKED ๋ฐฐ์ง |
in_progress |
ํ๋ ํค |
done |
์ด๋ก ํค, ์ฐ์๋จ โ CLEARED ๋ฐฐ์ง |
| just unlocked (1 ์ฌ์ดํด) | 0.9 ์ด scale+rotate+๋ ธ๋ ๊ฐ์กฐ ํ์ค |
๊ธฐ์กด ์ถ์ฒ ํจ๋์ NEXT QUEST ๋ก ๋ฆฌ์คํจ. ๊ทธ ์๋ SIDE QUESTS ์น์ ์ถ๊ฐ. ๋งจ ์๋ ์งํ ํํฉ.
โโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โถ NEXT QUEST (Top 3) โ
โ โโโโโโโโโโโโโโโโโโโโ โ
โ โ ๐จ ๋์์ธ score 8.2โ โ โ ์นด๋ ํค ํต์ผ
โ โ โ
โ
โ
โ
โ
โ โ
โ โ โท ํ๋ก ํธ, ํตํฉ ํด๊ธ โ โ
โ โโโโโโโโโโโโโโโโโโโโ โ
โ ... โ
โ โ
โ โก SIDE QUESTS (5) โ โ ์ ๊ท
โ โโโโโโโโโโโโโโโโโโโโ โ
โ โ โก ์ฐ์ ์ฌ๊ธฐ โ โ โ ๋จ์ผ ๋
ธ๋ ์ปดํฌ๋ํธ
โ โโโโโโโโโโโโโโโโโโโโ โ
โ ... โ
โ โ
โ โ ์งํ ํํฉ โ โ
โ ๋ฉ์ธ ํ์คํธ 4/12 โ
โ ์ฌ์ด๋ 2/5 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโ
NEXT QUEST ์นด๋:
- ๊ธฐ์กด ์ถ์ฒ ์ ์ + ๋ณ + ํ/snooze ์ก์ (#15) ๊ทธ๋๋ก ์ ์ง
- ์๊ฐ๋ง ํ์คํธ ์นด๋ ํค
- ํด๋ฆญ โ React Flow ์บ๋ฒ์ค์์ ํด๋น ๋ ธ๋๋ก ์นด๋ฉ๋ผ ์ด๋ + ๊ฐ์กฐ
SIDE QUESTS ์นด๋:
- icon + ์ ๋ชฉ๋ง (์ ์/๋ณ ํ์ ์ ํจ, ์งง์ ๊ฐ๋ก ์นด๋)
- ํด๋ฆญ โ ์บ๋ฒ์ค์์ ํด๋น ๋ ธ๋ ๊ฐ์กฐ
์งํ ํํฉ ๋ฐ์ค:
- ๋ฉ์ธ ํ์คํธ: ์ฌ์ด์ฆ โฅ 2 ์ธ ๋ชจ๋ ์ปดํฌ๋ํธ์ done/total ํฉ์ฐ
- ์ฌ์ด๋: ์ฌ์ด์ฆ == 1 ์ธ ์ปดํฌ๋ํธ์ done/total ํฉ์ฐ
-
์ ํ์คํฌ ๋ชจ๋ฌ: ์ ๋ชฉ ์
๋ ฅ ์์ ์ด๋ชจ์ง picker 1์ค. ์ถ์ฒ 8 ~ 12 ๊ฐ (
โก ๐จ ๐ป ๐ฌ ๐ ๐ ๐ฏ ๐ ๐ ๐ ๐ด ๐) + "๋ค๋ฅธ ์ด๋ชจ์ง" ์ ๋ ฅ. ๊ธฐ๋ณธ โก. - ํธ์ง ๋๋ก์ด: ๊ฐ์ picker ์ถ๊ฐ.
- prerequisite ๋น์ด์๋ ์ํ๋ก ์ ์ฅ โ ์๋์ผ๋ก ์ฌ์ด๋ ํ์คํธ ๋ถ๋ฅ (๋ณ๋ ํ ๊ธ ์์).
- ๋ ธ๋ ํด๋ฆญ โ ํธ์ง ๋๋ก์ด
- ๋ ธ๋ ๋๋ธํด๋ฆญ โ ์ํ ์ํ
- ๋ ธ๋ ๋ ๋๋๊ทธ โ ์์กด์ฑ ์ฃ์ง ์์ฑ
-
#6 Task CRUD API:
icon?: stringํ๋ ์ถ๊ฐ (์์ฉ/์ ์ฅ๋ง, ๊ฒ์ฆ ๋ถํ์).
| ์ด์ | ๋ณ๊ฒฝ |
|---|---|
| #15 ์ถ์ฒ ํ / Snooze | ๊ทธ๋๋ก |
| ์ญ์ , #22 ์ ํก์ | |
| #17 ๊ฐ์ค์น ์ฌ๋ผ์ด๋ | ๊ทธ๋๋ก |
| #18 ๋น/๋ก๋ฉ/์๋ฌ UI | ๊ทธ๋๋ก |
| #19 ๋ฐฐํฌ | ๊ทธ๋๋ก |
| #20 ํ๊ณ + ์ํฌํ๋ก์ฐ doc | ๊ทธ๋๋ก |
| #21 lib/quest.ts + ๋จ์ ํ ์คํธ (์ ๊ท) | ํ์ ํจ์ 4๊ฐ. TDD. โ 1์ผ. |
| #22 ํ์คํธ ์นด๋ ๋ ธ๋ (React Flow custom node) (์ ๊ท) | ์นด๋ ๋์์ธ + ์ํ๋ณ ๋ณํ + ํด๊ธ ํ์ค ์ ๋๋ฉ์ด์ . โ /D-n/๋งํ ํ๋ฆผ ํก์. โ 1.5์ผ. |
| #23 ์ข์ธก ํจ๋ ๋ฆฌ์คํจ + ์ฌ์ด๋ ํ์คํธ + ์ด๋ชจ์ง picker (์ ๊ท) | NEXT QUEST ๋ฆฌ์คํจ, SIDE QUESTS ์น์ , ์งํ ํํฉ, ๋ชจ๋ฌ/๋๋ก์ด picker. โ 1์ผ. |
ํฉ๊ณ: +2 ~ 2.5์ผ.
#21 (lib/quest.ts) โโฌโ #22 (์นด๋ ๋
ธ๋ โ REWARD ๋ฑ ์๋น)
โโ #23 (์ฌ์ด๋ ํ์คํธ ๋ถ๋ฅ โ connectedComponents ์๋น)
#22 โโ #8 (๊ธฐ์กด DAG ๋ ๋) ์ ํ์ฅ
#23 โโ #14 (๊ธฐ์กด ์ถ์ฒ ํจ๋) ์ ํ์ฅ
| ์ํ | ๋์ |
|---|---|
| Week 3 ๋นก๋นกํจ (+2 ~ 2.5์ผ) | Week 2 ๊ฐ 1์ผ์ด๋ผ๋ ๋ฐ๋ฆฌ๋ฉด #15(ํ/snooze) ๋๋ #17(๊ฐ์ค์น ์ฌ๋ผ์ด๋) ๋ฅผ Week 4 ํ ์ถ๊ฐ ์์ ์ผ๋ก ๋ฏธ๋ฃธ. |
| ํ์คํธ ์นด๋ ํค์ด ํฐ ๊ทธ๋ํ์์ ์๊ฐ ๋ ธ์ด์ฆ | ์คํ์ดํฌ #2 (React Flow + dagre) ์์ ์นด๋ ํค mock ์ผ๋ก ๊ฒ์ฆ. 50+ ๋ ธ๋์์ ๊ฒฌ๋๋์ง. |
| ํด๊ธ ํ์ค ์ ๋๋ฉ์ด์ ์ด ๋งค ํด๋ฆญ๋ง๋ค ๋ฐ๋๋์ด ์ฐ๋ง |
detectUnlocks ๊ฐ ์ํ๊ฐ just-transitioned ์ธ ๊ฒฝ์ฐ๋ง ๋ฐํ. ์ผ๋ฐ ๋ฆฌ๋ ๋์๋ ํธ๋ฆฌ๊ฑฐ ์ ๋จ. |
| REWARD ํ ์คํธ๊ฐ ํ์ ์ด๋ฆ ๊ธธ๋ฉด ์นด๋ ๊นจ์ง | 2 ~ 3๊ฐ ์ด๋ฆ ์ง์ ๋ ธ์ถ ํ ๊ทธ ์ด์์ "{N}๊ฐ ํด๊ธ" ์ผ๋ก ์ถ์ฝ. |
| ์ด๋ชจ์ง picker ๊ฐ ๋ชจ๋ฐ์ผ ํค๋ณด๋ UX ์ ์ถฉ๋ | 1์ฃผ์ฐจ์ ๋ชจ๋ฐ์ผ ๋ฐ์ํ์ ์ค์ฝํ ์ธ. ๋ฐ์คํฌํ ๊ธฐ์ค๋ง. |
Spike #2 (React Flow + dagre) ์์ ๊ฒ์ฆ๋ ๊ตฌ์ฒด ์ค์ ๊ฐ. #8 (DAG ๋ ๋), #22 (ํ์คํธ ์นด๋ ๋
ธ๋) ๊ตฌํ ์ ์์์ ์ผ๋ก ์ฌ์ฉ. ์ ์ฒด findings: spike/SPIKE.md (๋ธ๋์น).
const dagreConfig = {
rankdir: 'TB', // Top โ Bottom (LR ๋ ์ต์
)
nodesep: 12, // ๊ฐ๋ก ๊ฐ๊ฒฉ. ๊ธฐ๋ณธ 30 ๋๋ฌด spacious
ranksep: 28, // ์ธ๋ก ๊ฐ๊ฒฉ. ๊ธฐ๋ณธ 60 ๋๋ฌด spacious
ranker: 'tight-tree', // 'network-simplex' ๋ณด๋ค ์ปดํฉํธ
marginx: 16,
marginy: 16,
};์ด ๊ฐ์์ 50 ~ 80 ๋ ธ๋ ๊ทธ๋ํ๊ฐ ์๊ฐ์ ์ผ๋ก ๊ฒฌ๋ค. ๋ spacious ๊ฐ ํ์ํด์ง๋ฉด ์ฌ์ฉ์ ์ค์ ์ผ๋ก ๋ ธ์ถ ๊ฐ๋ฅ (3์ฃผ ์ค์ฝํ ์ธ).
dagre ์ ์ ๋ฌํ ๋ ธ๋ ๋ฐ์ค ์ฌ์ด์ฆ (์ค์ ๋ ๋ ์นด๋ ์ฌ์ด์ฆ์ ์ผ์น):
g.setNode(taskId, { width: 150, height: 76 });ํฐํธ: ๋ณธ๋ฌธ 11px / ๋ฉํยทREWARD 9px. ํจ๋ฉ 6/10 px. (์์ธํ ์นด๋ ๋์์ธ์ ยง5.1.)
<ReactFlow
fitView
fitViewOptions={{
padding: 0.08,
minZoom: 0.6, // ๊ธ์ ๊ฐ๋
์ฑ ํ๊ณ
maxZoom: 1.5,
}}
minZoom={0.1} // ์ฌ์ฉ์ ์ค ์์ ํ๊ณ
maxZoom={2}
/>fitViewOptions.minZoom: 0.6 ์ด ์ค์. ์ด ๊ฐ ์์ด fitView ์ฌ์ฉํ๋ฉด ๋
ธ๋ ์๊ฐ ๋์ด๋ ์๋ก ๊ธ์ ์ ๋ณด์ผ ์ ๋๋ก ์ถ์๋จ.
<MiniMap
pannable // ๋ฏธ๋๋งต ๋๋๊ทธ๋ก ๋ฉ์ธ ๋ทฐํฌํธ ์ด๋
zoomable // ์คํฌ๋กค๋ก ์ค
nodeStrokeColor="#37474f"
nodeStrokeWidth={1}
maskColor="rgba(15,23,42,0.55)"
style={{
backgroundColor: '#fff',
border: '2px solid #455a64',
borderRadius: 6,
boxShadow: '0 4px 12px rgba(0,0,0,0.18)',
}}
nodeColor={(n) =>
n.data?.status === 'done' ? '#a5d6a7'
: n.id === selectedId ? '#ff6f00'
: '#90a4ae'
}
/>pannable + zoomable ๋ก ๋ฏธ๋๋งต์ด ๋จ์ ํ์์์ ์ธํฐ๋ํฐ๋ธ ์ปจํธ๋กค๋ก ๊ฒฉ์. ํ
๋๋ฆฌ/๊ทธ๋ฆผ์/ํฐ๋ฐฐ๊ฒฝ ์ผ๋ก ๋ฉ์ธ ํ๋ฉด๊ณผ ๋ช
ํํ ๋ถ๋ฆฌ.
์ฃ์ง ๊ต์ฐจ๋ DAG ๊ตฌ์กฐ์์ ์ค๋ inherent ํน์ฑ์ด๋ผ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์์ ํ ์์จ ์ ์์. ๋์ "์ง๊ธ ๋ณด๊ณ ์ถ์ ์ฃ์ง๋ง" ๋ถ๋ฆฌํด์ ๋ณด๋ UX ๋ก ํด์:
const [selectedId, setSelectedId] = useState<string | null>(null);
// onNodeClick โ setSelectedId(node.id)
// onPaneClick โ setSelectedId(null)
const connectedIds = useMemo(() => {
if (!selectedId) return null;
const s = new Set([selectedId]);
edges.forEach(e => {
if (e.source === selectedId) s.add(e.target);
if (e.target === selectedId) s.add(e.source);
});
return s;
}, [selectedId, edges]);
// ๋
ธ๋: ๋น์ฐ๊ฒฐ์ opacity 0.18
// ์ฐ๊ฒฐ ์ฃ์ง: ์ฃผํฉ(#ff6f00) + strokeWidth 2.5 + animated
// ๋น์ฐ๊ฒฐ ์ฃ์ง: ํ์ + opacity 0.3์ด ์ธํฐ๋์ ์ #22 ์์ ํฌํจ (ํ์คํธ ์นด๋ ๋ ธ๋์ ์์ฐ์ค๋ฌ์ด ์ผ๋ถ๋ก ๊ตฌํ).
| ๋ ธ๋ ์ | layout ์๊ฐ (Mac M-series) |
|---|---|
| 50 | < 5 ms |
| 80 | < 10 ms |
| 120 | < 25 ms |
3์ฃผ ์ค์ฝํ(์์ญ ~ 100 ๋ ธ๋)์์ ์ถฉ๋ถ. 200+ ๋ ธ๋ ์ธก์ ์ Week 3 ํ๊ณ ์์.
- ์ค์ CRUD ์ ๊ฒฐํฉํ ์ธํฐ๋์ ์ฑ๋ฅ (์ ์ ๋ ๋๋ง ์ธก์ )
- ์ฌ์ดํด ์๋ ์ ๊ฑฐ๋ถ ํผ๋๋ฐฑ UX
- ๋ ธ๋ ์ถ๊ฐ/์ญ์ ์ ์๋ ๋ ์ด์์ ์ฌ๊ณ์ฐ์ ์๊ฐ์ ๋ถ๋๋ฌ์ (์ฆ์ vs ํธ๋์ง์ )
- ๋ชจ๋ฐ์ผ (์ค์ฝํ ์ธ)
- ๋คํฌ ๋ชจ๋ (์ค์ฝํ ์ธ)
- ๊ฒ์ด๋ฏธํผ์ผ์ด์ ํค์ด ์ค์ ์ฌ์ฉ์์ ์ฌ๋ฏธ ์ธ๊ฐ ๋ ธ์ด์ฆ ์ธ๊ฐ? (๋ณธ์ธ ์ฌ์ฉ ํ ํ๊ฐ)
- ์ฌ์ด๋ ํ์คํธ ์๋ ๋ถ๋ฅ๊ฐ ์ง๊ด์ ์ด์๋๊ฐ? ๋ณ๋ ํ ๊ธ์ด ํ์ํ๊ฐ?
- ํด๊ธ ์ ๋๋ฉ์ด์ ์ ๋น๋/๊ธธ์ด๊ฐ ์ ์ ํ๋๊ฐ?
- Week 3 ์๊ฐ ์์ฐ ๋๋น +2.5์ผ์ด ์ค์ ๋ก ์ผ๋ง์๋๊ฐ?