JUNYEONG’S LITTLE GALLERY / 2026

게임 기획 · 상세 개발 문서

요구사항에서 상태 모델, 핵심 알고리즘, 테스트와 배포까지.

01 · 제품 기획과 시나리오

초등학교 2학년 아이의 작품을 직접 조작하는 감상 경험으로 확장한다. 이름은 ‘준영의 작은 미술관’. 플레이어는 흩어진 밤하늘을 복원하는 작은 큐레이터다. 실패·감점·시간 제한 없이 관찰과 성취감을 제공한다. 게임 방식은 직사각형 이미지 조각의 자리 교환 퍼즐이며, 돌출부가 있는 전통 직소 퍼즐이나 빈칸 슬라이딩 퍼즐이 아니다.

입장 → 난이도 선택 → 조각 관찰·교환 ⇄ 완성 그림·찬스 → 완성 → 축하 → 고해상도 감상

02 · 요구사항 추적

요구구현검증 지점
그림 기반 퍼즐원본 전체를 동일한 격자로 분할, CSS 배경 좌표로 조각 표현완성 시 전체 그림과 일치
난이도6 / 12 / 24조각격자 3×2 / 4×3 / 6×4
제한 찬스판마다 3회, 상태에 잔여량 저장0회 또는 완성 시 비활성
완수 애니메이션별 파티클과 메달, 완료 통계 dialog모션 감소 설정 대응
원본 감상원본 JPEG, 포인터·휠·핀치·키보드100~800%, 이동 경계 제한
문서독립형 HTML 가이드·개발·후속 기획 3종download 링크와 인쇄 스타일
범용 접근반응형 정적 HTML/CSS/JS, 서버 API 없음데스크톱·모바일 뷰포트

03 · 기술 스택과 파일 구조

HTML5button, progress, dialog, semantic main/nav. 키보드와 스크린리더 레이블.
CSSGrid, 반응형 레이아웃, background-position, transform, reduced-motion.
JavaScript외부 라이브러리 없는 브라우저 코드. Pointer Events, localStorage, DOM API.
index.html        화면·다이얼로그·문서 다운로드
style.css         테마·반응형·축하 애니메이션
engine.js         순열·교환·힌트·저장 검증 (독립 함수)
app.js            상태·렌더링·저장·감상 제스처
assets/           원본 JPEG + 1600px 게임 이미지
docs/             자체 스타일을 포함하는 독립 HTML 3종
tests/engine.cjs   Node 기본 assert를 사용하는 규칙 테스트

게임용 1,600×1,200 이미지와 4,000×3,000 원본을 구분한다. 원본 파일은 수정 없이 복사한다. 문서에는 외부 의존성이 없고, 게임 전체 폴더를 내려받으면 인터넷 없이도 기본 플레이가 가능하다. 서비스 워커 캐시나 설치형 PWA는 포함하지 않는다.

04 · 아키텍처와 상태

사용자 입력 → app.js → engine.js → 상태 변경 → DOM 렌더링
         ↕ localStorage
감상 입력 → 포인터 맵 → 확대·이동 좌표 → 이미지 transform
{
  level: 0,                 // 난이도 인덱스 0..2
  tiles: [2, 5, 1, 0, 3, 4], // 위치 인덱스 → 원래 조각 번호
  hints: 3,                 // 0..3
  moves: 0,                 // 성공한 교환 + 찬스 사용 횟수
  elapsed: 0                // 활성 플레이 초
}

선택 조각, 감상 여부, 확대 배율은 일시적인 UI 상태다. 영구 키는 junyeong-starry-v1. 복원 전에 난이도, 조각 순열의 크기·중복·범위, 잔여 찬스, 시간과 횟수를 검사한다. 손상되었으면 기본 난이도로 초기화한다. 저장 예외는 게임 진행을 방해하지 않는다. 여러 탭 간 동기화·서버 백업은 지원하지 않으며 마지막 저장이 우선한다.

05 · 핵심 알고리즘

A. 섞기와 항상 풀 수 있는 규칙

Fisher–Yates로 전체 순열을 만든 뒤 제자리에 남은 고정점을 다른 위치와 교환한다. 시작부터 맞은 조각이 없도록 보정한다. 두 임의 조각을 교환할 수 있어 슬라이딩 퍼즐의 홀짝 제약이 없다. 이미 맞은 조각을 잠가도 남은 순열은 교환만으로 풀 수 있다. 시간·공간 복잡도는 O(n)이다. 보정 때문에 모든 순열이 균등 확률로 나오지는 않지만 교육용 게임에서 요구하지 않는다.

B. 조각 렌더링

column = piece % cols
row = floor(piece / cols)
background-size = (cols × 100)% (rows × 100)%
background-position = (column / (cols−1) × 100)%
                      (row / (rows−1) × 100)%

판의 종횡비는 원본과 같은 4:3이다. 각 버튼 배경으로 동일한 이미지의 다른 구간을 표시하여 개별 조각 파일과 캔버스 추출 없이 동작한다.

C. 교환과 찬스

선택 A → 선택 B
if A ≠ B and tiles[A] ≠ A and tiles[B] ≠ B:
    swap(tiles[A], tiles[B])
    moves += 1

찬스:
    target = 첫 번째 틀린 위치
    source = tiles.indexOf(target)
    swap(tiles[target], tiles[source])
    hints -= 1; moves += 1

찬스는 최소 한 조각을 맞추며 이미 맞은 조각을 훼손하지 않는다. 매 이동마다 정답 수를 재계산하고 전부 맞으면 한 번의 완료 대화상자를 연다. 완성 판을 다시 로드하면 완성 상태를 보존하며 축하 창을 반복 표시하지 않는다.

D. 확대와 좌표 보존

base = min(viewportWidth / imageWidth, viewportHeight / imageHeight)
nextZoom = clamp(zoom × factor, 1, 8)
ratio = nextZoom / zoom
x = pointerX − (pointerX − x) × ratio
y = pointerY − (pointerY − y) × ratio
transform = translate(x, y) scale(base × nextZoom)

포인터 아래의 이미지 지점을 유지하면서 확대한다. 두 포인터의 거리 비율을 핀치 배율로, 중점 변화를 이동량으로 쓴다. 포인터 취소·캡처 해제도 정리한다. 이미지가 화면보다 작으면 중앙 정렬하고, 크면 빈 공간이 드러나지 않도록 경계를 제한한다. 회전·창 크기 변경 때 화면 맞춤으로 재설정한다.

06 · UI/UX와 접근성

남색 미술관과 따뜻한 종이색 프레임, 금빛 보상을 사용한다. 650px 이하에서는 보드와 도구를 세로로 배치한다. 드래그 정밀도 대신 두 번의 탭으로 조작한다. 정답은 색뿐 아니라 별과 레이블로 구분한다. 모든 동작 버튼은 키보드 조작 가능하며 동적 상태는 polite live region으로 전달한다. 감상 영역에는 터치 기본 스크롤을 막고 게임 페이지의 스크롤은 유지한다. 팝업은 native dialog로 초점과 Escape 닫기를 제공한다.

07 · 검증과 운영

node tests/engine.cjs로 세 난이도의 섞기 불변식, 찬스의 단조 증가, 교환 잠금, 저장 데이터 검증을 실행한다. node --check app.js로 문법을 확인한다. 브라우저 검증 내용과 한계는 README의 검증 기록을 참조한다.

수동 회귀 항목합격 기준
난이도 변경·취소취소 시 기존 판 유지, 확인 시 모든 통계 초기화
찬스 4번째 요청3회까지만 사용, 남은 횟수 0
새로고침동일 순열·찬스·통계 복원
모바일가로 넘침 없이 탭과 스크롤 가능, 핀치 확대
완성축하 표시, 감상 복귀, 새 판 가능
저장 차단·손상예외 없이 새 판 실행

로컬: python3 -m http.server 8000 실행 후 localhost:8000에 접속한다. 배포는 현재 폴더 전체를 일반 정적 호스팅에 올린다. 빌드·환경 변수·DB·비밀키·유료 서버가 필요 없다. 원본 사진의 공개 여부는 배포 전 보호자가 결정할 수 있다. 이 구현은 정적 파일로 배포한다. 기존 Cloudflare 계정의 Workers Static Assets를 사용하며 별도 게임 서버나 데이터베이스를 생성하지 않는다.

최신 주요 브라우저를 대상으로 한다. OS별 실제 장치 전수 검증은 별도 필요하다. 타이머는 초 단위 UI용이며 경쟁 기록·정밀 측정 용도가 아니다. 자동 저장은 기기 내에만 남는다.