| name | sokoban |
| description | Flutter 기반 Sokoban(창고지기) 퍼즐 게임 개발 스킬. 격자 기반 맵에서 플레이어가 상자를 밀어 목표 지점에 배치하는 클래식 퍼즐 게임을 구현한다. Sokoban 게임의 맵 파싱, 이동 로직, 충돌 감지, 상자 밀기, 승리 판정, Undo, 레벨 관리, UI 렌더링을 포함한다. Flutter로 Sokoban 게임을 만들거나, 레벨을 추가하거나, 게임 로직을 수정하거나, UI를 개선할 때 사용한다.
|
Sokoban 게임 개발 스킬
개요
Sokoban(창고지기)은 격자 기반 퍼즐 게임으로, 플레이어가 상자를 밀어서 모든 목표 지점에
배치하면 클리어된다. 상자는 밀기만 가능하고 당길 수 없으며, 한 번에 하나의 상자만 밀 수 있다.
핵심 메커니즘 (엔진 분석 기반)
- 6가지 엔티티 타입: player(플레이어), box(상자), boxOnGoal(목표 위의 상자), goal(목표지점), wall(벽), floor(바닥)
- 상자 ID 시스템: 각 상자를
{"x,y": boxId} 형태로 고유 ID로 추적하여 개별 상자의 이동 경로를 관리
- Last Tile 패턴: 원본 엔진은
playerLastTile, boxesLastTiles로 엔티티가 이동한 후 원래 바닥 타일(floor/goal)을 추적 — Flutter 버전에서는 2레이어 분리로 이 패턴이 불필요
- 승리 판정: 모든 목표지점(goal) 좌표에 상자가 놓여 있는지 확인
- 레벨 초기화: 원본 레벨 데이터의 깊은 복사(deep copy)로 원래 상태 보존
엔진 분석 기반 설계 결정
원본 JavaScript 엔진(ref/Sokoban-Engine/)을 CoT/ToT 정밀 분석한 결과, Flutter 변환 시 다음과 같이 설계를 최적화한다.
| 항목 | 원본 엔진 (JavaScript) | Flutter 최적화 설계 | 이점 |
|---|
| 상태 관리 | 가변 2D 배열 직접 수정 | 불변 상태 객체 (Immutable State) | Undo가 스택 push로 자연스러움 |
| 타일 추적 | lastTile 패턴으로 원래 바닥 추적 | 2레이어 분리 (floors 고정 + objects 동적) | lastTile 추적 불필요, 로직 단순화 |
| 이동 처리 | moveUp/Down/Left/Right 4개 별도 메서드 | tryMove(Direction) 단일 메서드 + Direction enum | 코드 중복 제거, 유지보수 용이 |
| 플레이어 위치 | 매 이동마다 전체 배열 스캔 O(n*m) | playerPos 직접 저장 O(1) | 성능 최적화 |
| 엔티티 표현 | 이모지 문자열 ("😄", "📦" 등) | TileType enum | 타입 안전성, 비교 효율 |
| 데드락 감지 | 미구현 | DeadlockDetector 별도 클래스 | 사용자 경험 향상 |
| 레벨 생성 | sokoban-generator 라이브러리 | XSB 표준 레벨 형식 파싱 | 외부 레벨 호환성 |
프로젝트 구조
lib/
├── main.dart # 앱 진입점
├── models/
│ ├── tile_type.dart # 타일 타입 enum (6가지 엔티티)
│ ├── position.dart # 좌표 모델 (x, y 불변 객체)
│ ├── direction.dart # 방향 enum (up, down, left, right + dx/dy 오프셋 + opposite)
│ ├── game_action.dart # GameAction (Move/Push 분리, ActionType enum)
│ └── level.dart # 레벨 데이터 모델
├── game/
│ ├── game_state.dart # 게임 상태 관리 (핵심 — 불변 상태, 2레이어)
│ ├── level_parser.dart # 레벨 문자열 → 맵 데이터 변환 (XSB 파싱)
│ └── deadlock_detector.dart # 데드락 감지 (코너/벽라인 데드락)
├── widgets/
│ ├── game_board.dart # 게임 보드 렌더링 위젯 (CustomPainter)
│ ├── game_screen.dart # 게임 화면 (보드 + 컨트롤 + 상태 표시)
│ └── level_select.dart # 레벨 선택 화면
└── data/
└── levels.dart # 레벨 데이터 저장소 (XSB 형식)
핵심 규칙
엔티티 타입 (6가지)
| 타입 | XSB 문자 | 설명 | 레이어 |
|---|
| wall | # | 벽 — 이동 불가 | floors |
| floor | (공백) | 빈 바닥 | floors |
| goal | . | 목표지점 | floors |
| player | @ | 플레이어 | objects |
| box | $ | 상자 | objects |
| boxOnGoal | * | 목표 위의 상자 | objects (floors에 goal 유지) |
| playerOnGoal | + | 목표 위의 플레이어 | objects (floors에 goal 유지) |
이동 규칙
- 기본 이동: 플레이어는 상하좌우(4방향)로 이동 가능
- 벽 충돌: 이동 목표 칸이 벽이면 이동 불가
- 상자 밀기: 이동 목표 칸에 상자가 있고, 상자 너머 칸이 빈 공간(floor) 또는 목표지점(goal)이면 상자를 밀 수 있음
- 밀기 불가: 상자 너머에 벽 또는 다른 상자가 있으면 밀 수 없음
- 단일 밀기: 한 번에 하나의 상자만 밀 수 있음 (연쇄 밀기 불가)
- 당기기 불가: 상자는 밀기만 가능하고 당길 수 없음
이동 처리 (tryMove 단일 메서드)
tryMove(Direction dir) 의사코드:
1. nextPos = playerPos + dir.offset
2. if floors[nextPos] == wall → 이동 불가
3. if objects[nextPos] == box:
a. beyondPos = nextPos + dir.offset
b. if floors[beyondPos] == wall 또는 objects[beyondPos] == box → 이동 불가
c. 상자를 beyondPos로 이동, 플레이어를 nextPos로 이동
4. else: 플레이어를 nextPos로 이동
5. 새 불변 상태 반환, 이전 상태를 Undo 스택에 push
승리 조건
- 모든 목표지점(goal) 좌표에 상자가 놓이면 클리어
- 파싱 시 목표지점 좌표 목록(
goalPositions)을 미리 저장하여, 매 이동 후 해당 좌표들만 검사
데드락 조건
상자가 목표지점에 도달할 수 없는 상태. 자동 감지하여 사용자에게 알림:
-
코너 데드락 (활성화): 상자가 두 면이 벽인 코너에 갇힌 경우 (목표지점이 아닌 곳에서)
#? ?# ?? ??
?$ $? $? ?$
?? ?? ?# #?
(여기서 ?는 임의 타일, 대각선 두 면이 벽이면 코너 데드락)
-
벽라인 데드락 (비활성화 — false positive 빈번): 벽변 데드락 알고리즘은 코드에 구현되어 있으나 detectDeadlocks()에서 호출하지 않음. 대칭 맵 등에서 풀 수 있는 상태를 데드락으로 잘못 감지하는 false positive가 빈번하기 때문.
isWall() 주의: FloorType.empty도 벽으로 취급해야 함. XSB 파싱 시 불규칙한 맵의 빈 영역이 empty로 남는데, 이를 벽으로 취급하지 않으면 이동 로직이 부정확해짐
개발 워크플로우
1단계: 모델 정의
타일 타입(6가지 엔티티), 좌표(불변 Position), 방향(Direction enum + dx/dy 오프셋) 등 기본 데이터 모델을 정의한다.
상세 구현은 game-logic.md의 모델 정의 섹션 참조.
2단계: 레벨 파서 구현
표준 XSB 레벨 문자열을 2레이어(floors + objects) 2D 맵 배열로 변환하는 파서를 구현한다.
파싱 시 playerPos와 goalPositions를 추출하여 별도 저장한다.
레벨 형식과 기본 레벨 데이터는 levels.md 참조.
3단계: 게임 로직 구현
tryMove(Direction) 단일 메서드로 이동, 충돌 감지, 상자 밀기, 승리 판정 로직을 구현한다.
불변 상태 패턴으로 매 이동마다 새 GameState를 생성하고, 이전 상태를 Undo 스택에 push한다.
핵심 알고리즘과 소스코드는 game-logic.md의 GameState 구현 섹션 참조.
4단계: 데드락 감지 구현
DeadlockDetector 클래스를 구현하여 코너 데드락과 벽라인 데드락을 감지한다.
매 상자 이동 후, 이동된 상자의 위치에 대해서만 데드락 검사를 수행한다 (전체 스캔 불필요).
데드락이 감지되면 사용자에게 시각적 알림을 제공하고, Undo를 권장한다.
5단계: UI 렌더링
CustomPainter로 게임 보드를 렌더링하고, 스와이프/키보드 입력을 처리한다.
2레이어를 순서대로 그린다: floors 레이어 먼저, objects 레이어를 그 위에.
데드락 상태의 상자는 시각적으로 구분(예: 빨간색 강조)한다.
렌더링 패턴과 입력 처리는 ui-rendering.md 참조.
6단계: 레벨 시스템
레벨 선택, 클리어 상태 저장, 다음 레벨 진행을 구현한다.
이동 횟수, 밀기 횟수 등 통계를 추적한다.
핵심 설계 원칙
- 불변 상태: GameState는 불변 객체로 관리. 이동 시 새 상태를 생성하여 Undo 스택에 push
- 2레이어 분리: floors(벽, 바닥, 목표지점 — 고정)와 objects(플레이어, 상자 — 동적)를 분리
- Direction enum 일반화:
tryMove(Direction) 단일 메서드로 4방향 처리
- playerPos 직접 저장: O(1)로 플레이어 위치 접근
- Action 분리 (Move/Push): 이동과 밀기를 명확히 구분하여 LURD 표기, 통계, Push 단위 Undo 지원 (sokoban-rs 기반)
- 이벤트 기반 아키텍처: BoxEnterGoal, BoxLeaveGoal, LevelSolved 등의 이벤트로 게임 로직과 UI/사운드 분리 (sokoban-rs 기반)
- 관심사 분리: 게임 로직(game/)과 UI(widgets/)를 분리. 로직은 Flutter 의존성 없이 순수 Dart로 구현
- CustomPainter 사용: 성능과 유연한 렌더링을 위해 GridView 대신 CustomPainter 권장
- 표준 레벨 형식: 국제 표준 Sokoban 레벨 형식(XSB) 사용으로 외부 레벨 호환
- 프리즈 데드락 감지: 재귀적 프리즈 감지로 코너/벽변/2x2 데드락 완전 감지 (sokoban-rs 기반)
에이전트 모드
에이전트 파일은 agents/ 폴더에 있으며, 각 에이전트는 독립적으로 실행 가능하다.
모든 에이전트를 동시에 실행하려면 프로젝트 루트의 CLAUDE.md를 참조한다.
| 에이전트 | 역할 | 파일 |
|---|
| game-engine | 핵심 게임 로직 (이동, 충돌, Undo/Redo, 데드락) | game-engine-agent.md |
| level-system | 레벨 파싱, 저장, 최고 기록 관리 | level-system-agent.md |
| solver | A* 자동 풀이기 | solver-agent.md |
| auto-move | 탭 기반 자동 이동, 박스 Push 경로 | auto-move-agent.md |
| ui-rendering | UI 렌더링, 애니메이션, 입력 처리 | ui-rendering-agent.md |
| state-management | 상태 관리, 이벤트 시스템, 설정 | state-management-agent.md |
| integration | 전체 통합, main.dart, 빌드/테스트 | integration-agent.md |
참조 문서
- 게임 로직 상세: game-logic.md — 모델 정의, GameState 핵심 로직, 이동 알고리즘, Undo 구현, 2레이어 분리 패턴, sokoban-rs 기반 고급 개선
- 레벨 데이터: levels.md — XSB 레벨 형식, 레벨 파서, 기본 레벨 데이터
- UI 렌더링: ui-rendering.md — CustomPainter 렌더링, 입력 처리, 게임 화면 구성
- A 풀이기*: solver.md — A* 알고리즘, 휴리스틱, 상태 정규화, 프리즈 데드락, 터널 감지 (sokoban-rs 기반)
- 자동 이동: auto-move.md — BFS 경로 탐색, 박스 Push 경로 계산, 상태 머신 (sokoban-rs 기반)
- 고급 기능: advanced-features.md — Action 분리, Push 단위 Undo/Redo, 이벤트 시스템, 입력 버퍼링, 최고 기록, Lerp 보간 (sokoban-rs 기반)
- 원본 엔진:
ref/Sokoban-Engine/ — JavaScript 기반 원본 Sokoban 엔진 (설계 참조용)
- Rust 엔진:
ref/sokoban-rs/ — Bevy/ECS 기반 Sokoban 엔진 (고급 기능 참조용)