
복권 2,000만 장에서 1등을 찾는 시뮬레이터를 만들었다
요약
2,000만 장의 복권 데이터를 효율적으로 시뮬레이션하는 Web 앱 개발 사례를 소개합니다. Canvas를 활용한 렌더링 최적화와 해시 기반의 경량 상태 관리 기법을 다룹니다.
핵심 포인트
- Canvas를 사용하여 2,000만 개의 DOM 요소 생성 없이 렌더링 최적화
- Map과 좌표 해시(Hash)를 이용한 대규모 데이터의 경량 상태 관리
- 카메라 배율에 따른 LOD(Level of Detail) 방식의 그리기 전략 적용
- 복권의 기대값과 환급률을 시각화하여 사용자 경험 제공
복권의 당첨 확률은 숫자로만 봐서는 실감이 잘 나지 않는다.
그것을 체험할 수 있는 Web 앱을 만들었다.
본 기사에서는 얼마나 당첨되지 않는지에 대한 숫자와, 앱의 기능 및 조작법을 정리한다.
실제로 존재하는 고액 복권의 '1 유닛(Unit)'은 20,000,000장(2,000만 장)으로 구성된다.
등급과 수량의 예시는 다음과 같다.
| 등급 | 금액 | 수량 | 당첨 확률 |
|---|---|---|---|
| 1등 | 7억 엔 | 1 | 1 / 20,000,000 |
| ... |
1장당 300엔이므로, 1 유닛을 모두 사면 6,000,000,000엔(60억 엔)의 투자가 필요하다.
반면, 전체 수량 × 금액의 합계 환급금은 약 26억 엔이다. **환급률은 약 43%**로, 사면 살수록 기대값(Expected Value) 베이스로는 손해를 보는 구조로 되어 있다.
체감하기 쉬운 숫자를 나열해 본다.
- 1만 장(300만 엔 분량)을 사더라도, 1등이 당첨될 확률은 1/2,000.
- 최하위 등급(300엔)은 약 1,000장당 약 30만 엔의 환급. 투자한 300만 엔 중 돌아오는 것은 1할.
- 1등을 노리려면 평균적으로 2,000만 장(60억 엔)이 필요하다는 거친 계산.
이것을 화면으로 보여주는 것이 본 앱이다.
20열 × 20행 = 400 에리어로 분할하고, 각 에리어에 250 × 200 = 50,000장을 배치한다. 합계 20,000,000장. 모두 Canvas로 그리고 있으며, DOM 요소는 생성하지 않는다.
처음 표시되는 다이얼로그는 「뒤집기 / 대량 구매 / 1등 찾기」의 3가지 아이콘과 시작 버튼만 있는 심플한 구성이다.
- 1등(1장)과 전후상(앞뒤 보너스 상금)·2등·3등·4등의 소수 상위 등급은 기동 시 좌표를 확정하여
Map에 유지. - **5등(약 1/100)과 최하위 등급(약 1/10)**은 좌표 해시(Hash)로 판정하여, 200만 건의 좌표를 유지하지 않음.
이로써 2,000만 장 분량의 상태 관리를 경량으로 수행하고 있다.
줌 인(Zoom-in)을 하면, 각 권은 가로가 긴 티켓 형태(오렌지 + 핑크색 번호면 + 빨간 띠)로 그려진다. 뒤집은 결과는 등급별로 색상을 구분하고, ★를 중앙에 배치한다.
헤더 우측 하단이 아니라, 메인 영역의 우측 상단에 상시 표시되는 소형 패널.
각 등급의 도안(범례)과 금액, 현재 당첨 수량을 일람 표시한다.
0개인 등급은 흐리게 표시하여, 당첨된 행이 눈에 띄게 한다.
헤더에 「매수 / 투자 / 환급 / 손익」을 상시 표시한다.
손익은 색상으로 구분(빨강 = 손실, 초록 = 이익)하며, 살 때마다 작게 확대되는 bump 애니메이션으로 변화를 보여준다.
수동 탭으로 선택한 1장만 가로 회전(scaleX로 1→0→1)하며 뒤집는다.
폭買い(대량 구매, 1만 장)는 순식간에 처리하며, 플립(Flip) 연출을 넣지 않는다(불필요한 부하를 발생시키지 않기 위함).
권에 마우스를 올리면, 해당 권이 당첨권인 경우에만 등급과 금액을 툴팁(Tooltip)으로 표시한다.
꽝이거나 권 사이의 틈새에서는 나타나지 않는다.
버튼을 누르면 현재 카메라 위치에서 1등의 좌표로 부드럽게 애니메이션 이동한다(위치는 ease-in-out, 배율은 로그 보간).
도착하면 금색 권과 집중선이 표시된다.
랜덤한 좌표에서 1만 장을 일괄 구매한다.
3,000,000엔 분량. 실행 후 푸터(Footer)에 「당첨 수량」과 「환급 금액」이 일시적으로 표시된다.
줌 아웃(Zoom-out) 시에는 에리어 단위의 채우기만 수행하고, 줌 인(Zoom-in) 시에만 개별 권을 그린다.
광역에서도 개별 권을 전부 그리려고 하면 수백만 번의 그리기 명령이 발생하므로, 카메라 배율에 따라 전환한다.
블록의 색이 검은색에 가까워질수록, 해당 에리어 내의 뒤집은 매수가 많음을 나타낸다.
| 조작 | 방법 |
|---|---|
| 이동 | 드래그 / 스와이프 |
탭 판정은 세계 좌표에서 권의 그리드(Grid)로 사상(Mapping)하여 수행한다.
너무 줌 아웃되어 뒤집을 수 없는 경우, 경고를 띄우는 대신 탭 지점을 향해 자동으로 줌 인한다(조작의 헛스윙을 방지하기 위함).
최초 기동 시에는 중앙 부근의 권에 스케일 1로 밀착된 상태로 시작하며, 손가락 아이콘이 점멸하며 표시된다.
약 1초간 액션이 없으면, 고스트가 손가락을 움직이며 3장을 뒤집어 보여준다.
직접 탭하면 손가락 아이콘은 사라지고 일반 조작으로 돌아간다.
- 프론트엔드는 단일
index.html. - 프레임워크 없이 Canvas 2D만 사용.
- 그리기(Rendering)는 카메라 배율에 따라 LOD(Level of Detail)를 전환. 가시 사각형 컬링(Culling)을 통해 화면 밖의 권은 그리지 않음.
- 5등·최하위 등급 판정은 좌표 해시 사용. 고정 좌표를
Map으로 가지는 것은 상위 등급뿐(합계 5,000건 이상). - 상태는
Set(구매 완료)과Map으로 관리.
(당첨)으로 유지. 블록별로 이미 넘긴 매수도 별도로 캐싱하여, 광역 렌더링 시의 어두운 정도를 결정. - 아이콘은 원본 이미지로부터 System.Drawing을 사용하여 여러 크기로 생성. 작은 사이즈는 중앙의 고래 쪽으로 크롭하여 가독성을 확보. - 호스팅은 Cloudflare Pages, 배포는 Wrangler CLI (npm run deploy) 사용.
소스가 단일 파일이며 프레임워크를 사용하지 않으므로, 개조나 이식도 용이함.
1 유닛의 이론적인 환급률은 약 43%.
100만 엔을 사용하면 평균적으로 약 43만 엔이 돌아오고, 57만 엔이 손실이 됨. 이것이 복권을 "자산 형성 수단"이라 부를 수 없는 이유.
다만, 1등 7억 엔이라는 "분산이 큰 이상치 (Outlier)"에 거는 오락으로서 선을 긋는다면, 300엔으로 7억 엔의 보물을 하루 동안 맡겨두고 꿈을 꾸는 것이라는 가성비는 나쁘지 않다는 관점도 가능함.
본 앱은 전자(숫자의 현실)를 시각적으로 강요하지 않으면서 확인할 수 있는 도구.
- 고액 복권 1 유닛 = 2,000만 매를 화면상에서 재현하여, 넘겨보는 경험을 제공.
- 1등은 1매 (1/2,000만). 5등·최하위 등수는 좌표 해시 (Coordinate Hash)로 판정하여 가볍게 동작.
- 투자·환급·손익, 당첨 내역을 상시 표시하여 확률의 편차를 숫자로도 보여줌.
- 단일 HTML, Canvas 2D만 사용. Cloudflare Pages로 배포.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기