작성일 2026-06-13 · 프로그래밍을 막 시작한 분도 이해할 수 있도록 풀어쓴 개발 일지입니다. 더 기술적인 문서는 CLAUDE.md, 원본 설계도는 Requirement.md를 보세요.
"참가자 이름과 경품을 입력하면, 돌림판을 돌려서 당첨자를 정해주는 웹페이지"
행사장에서 큰 화면에 띄워놓고 쓰는 추첨기입니다. 서버(중앙 컴퓨터)도, 데이터베이스도 필요 없이 브라우저 하나로 완결되는 앱입니다.
코드를 짜기 전에 Requirement.md라는 요구사항 문서(건축으로 치면 설계도)를 먼저 읽었습니다. 거기엔 단순한 "돌림판" 이상의 규칙들이 숨어 있었어요. 예를 들면:
- 돌림판 칸 = 남은 경품 수 + "꽝" 1칸 (경품이 줄수록 꽝이 나올 확률이 자연스럽게 올라감)
- 한 번 당첨된 사람은 다시 못 뽑힘
- 경품이 하나 소진되면 돌림판을 다시 섞어서 새로 그림
이런 "눈에 잘 안 띄는 규칙"을 미리 정리한 CLAUDE.md를 만들어 두었습니다.
요구사항 문서는 만드는 도구를 정해주지 않았습니다. 그래서 딱 하나, 기술 선택을 질문했고 **"순수 HTML + TypeScript + SVG"**로 결정했습니다. 각각을 쉽게 설명하면:
| 도구 | 쉬운 설명 |
|---|---|
| HTML | 웹페이지의 뼈대 (글자·버튼이 어디 있는지) |
| TypeScript | 동작을 적는 언어. 자바스크립트에 "오타·실수 검사기"가 붙은 버전 |
| SVG | 그림을 그리는 도구. 돌림판의 부채꼴 칸들을 수학으로 정확히 그림 |
💡 알아두기 TypeScript는 브라우저가 바로 못 읽어서 번역(컴파일) 과정이 필요합니다. 보통은 "번들러"라는 복잡한 도구를 쓰지만, 우리는
tsc라는 기본 번역기만 써서 단순하게 갔습니다. 비결은 코드에서 파일을 부를 때./draw.js처럼.js확장자를 꼭 붙인 것 — 그래야 번역된 결과를 브라우저가 그대로 읽을 수 있습니다.
집을 지을 때 기초 → 골조 → 마감 순서로 가듯, 코드도 속(논리)부터 겉(화면)으로 쌓았습니다.
① 뼈대 세우기 → 빈 페이지, 설정 파일, 디자인 색/글꼴
② 데이터 모양 정의 → "참가자·경품·결과가 어떻게 생겼는지" (types.ts)
③ 추첨 두뇌 → 돌림판 칸 만들기·섞기·결과 뽑기 (draw.ts) ★핵심
④ 상태 관리 → "지금 누가 남았고 경품이 몇 개인지" (state.ts)
⑤ 돌림판 그림 → SVG로 부채꼴 그리고 회전 (wheel.ts)
⑥ 화면과 버튼 → 사람이 보고 누르는 부분 (ui.ts + main.ts)
⑦ 저장·내보내기 → 새로고침 대비, CSV 다운로드 (storage.ts)
⑧ 검사 → 진짜 잘 도는지 확인
화면(겉)을 마지막에 둔 이유는, 두뇌(③④)가 먼저 정확해야 화면이 그걸 그냥 보여주기만 하면 되기 때문입니다.
7개 파일은 각자 하나의 역할만 맡습니다. 아래 표에서 "참조하는 파일"은 그 파일이 불러다 쓰는(import) 다른 파일입니다.
| 파일 | 하는 일 | 참조(import)하는 파일 |
|---|---|---|
| src/types.ts | 데이터의 "모양"만 정의 (참가자·경품·결과·상태가 어떤 형태인지) | 없음 |
| src/draw.ts | 추첨 계산: 돌림판 칸 만들기·섞기·결과 뽑기 | types |
| src/state.ts | 현황 관리: "누가 남았나·경품 몇 개·끝났나"를 계산 | draw, types |
| src/storage.ts | 브라우저에 현재 상태 저장/복원 (새로고침 대비) | types |
| src/wheel.ts | 돌림판 그림(SVG)을 그리고 회전시킴 | 없음 (브라우저 기능만 사용) |
| src/ui.ts | 화면 그리기 + 버튼·입력 연결 | draw, state, wheel, types |
| src/main.ts | 전체 지휘: 상태 보관, 회전 흐름 조율, 저장/내보내기 | draw, state, storage, ui, wheel, types |
화살표 ─►는 "왼쪽이 오른쪽을 불러다 쓴다"는 뜻입니다. 항상 위 단계가 아래 단계를 부르고, 그 반대는 없습니다.
지휘 main.ts ─► ui.ts, state.ts, storage.ts, draw.ts, wheel.ts (전부 불러 씀)
화면 ui.ts ─► draw.ts, state.ts, wheel.ts
현황 state.ts ─► draw.ts
기초 draw.ts ─► types.ts storage.ts ─► types.ts
재료 types.ts (모양 정의 — 아무것도 안 부름) wheel.ts (독립 — 우리 파일 안 부름)
main.ts가 유일하게 모든 파일을 부르는 "맨 위" 지휘자입니다.types.ts는 모두가 갖다 쓰지만 자신은 아무것도 안 부르는 "맨 아래" 토대입니다 (데이터 모양만 정의).wheel.ts는 우리 파일을 하나도 안 부르는 독립 부품입니다 (브라우저의 SVG 기능만 씀).ui.ts와main.ts가 이걸 갖다 씁니다.
draw.ts·state.ts는 화면(브라우저)을 전혀 부르지 않습니다. 그래서 추첨 로직만 떼어 따로 테스트할 수 있었습니다(아래 7번).
초보자에게도 흥미로운, 이 앱의 핵심 아이디어 둘입니다.
보통 생각하면 "돌림판을 돌려서 → 멈춘 곳이 결과"일 것 같죠. 하지만 우리는 반대로 했습니다.
먼저 컴퓨터가 "3번 칸 당첨!"을 몰래 정함 → 그 다음 3번 칸이 포인터 아래 오도록 회전 각도를 계산해서 돌림.
왜냐? 요구사항이 "애니메이션은 연출일 뿐, 결과는 조작 불가능해야 한다"고 했기 때문입니다. 결과와 보여주기를 분리한 거예요.
화면을 새로 그리면 돌림판이 처음 위치로 리셋되어 버립니다. 그래서 회전하는 동안에는 버튼만 잠그고, 회전이 완전히 끝난 뒤에야 결과를 반영해 화면을 갱신합니다.
말로만 "됐다"고 하지 않고 3단계로 검증했습니다:
- 문법 검사 —
tsc로 컴파일 → 오류 0개 (오타·타입 실수 없음) - 두뇌 검사 — 주방장(추첨 로직)을 따로 떼어 11가지 상황을 테스트:
- "경품 3개면 돌림판 칸이 4개(꽝 포함)인가?" ✓
- "당첨된 사람이 또 당첨되지 않는가?" ✓
- "경품 다 떨어지면 자동 종료되는가?" ✓
- → 11개 전부 통과
- 배달 검사 — 실제 웹서버로 띄워서 페이지·코드·스타일이 정상 응답(200)하는지 확인 ✓
완성도: 요구사항의 모든 핵심 기능이 구현되고 로직 검증까지 끝난 상태입니다.
npm install # 처음 한 번 (번역기 설치)
npm run build # 코드 번역 (TypeScript → 브라우저용 JavaScript)
npm run serve # 브라우저에서 http://localhost:5173 접속개발 중에는
npm run watch(자동 번역)와npm run serve(서버)를 각각 다른 터미널에서 함께 켜두면 편합니다.
아직 안 한 것 두 가지:
- 실제 화면을 눈으로 확인 — 돌림판이 부드럽게 도는지 브라우저로 직접 보기
- 글꼴 완전 오프라인화 — 지금은 인터넷에서 글꼴을 받아오고, 없으면 기본 글꼴로 대체됨
| 용어 | 뜻 |
|---|---|
| 컴파일 | 사람이 쓴 코드(TypeScript)를 브라우저가 읽는 코드(JavaScript)로 번역하는 일 |
| ESM (ES 모듈) | 코드 파일을 서로 불러 쓰는 표준 방식 (import) |
| SVG | 도형을 수학 좌표로 그리는 이미지 형식 (확대해도 안 깨짐) |
| 상태(state) | "지금 누가 남았고 경품이 몇 개인지" 같은 현재 상황 데이터 |
| 순수 함수 | 같은 입력엔 항상 같은 출력을 주고, 바깥 세상을 안 건드리는 함수 → 테스트하기 쉬움 |
| localStorage | 브라우저가 데이터를 저장해두는 작은 공간 (새로고침해도 안 사라짐) |